0
\$\begingroup\$

In Unity we can use the Attribute [SerializeField] to expose private fields to the Unity inspector like so:

[SerializeField]
private float speed;

Now I want to do the same for methods that I want to use in the inspector: for example OnClick callback methods for buttons. Using [SerializeField] does not work and from what I can tell there is no [SerializeMethod] or similar to expose this functionality. How can I expose private methods to the Unity inspector similar to how [SerializeField] exposes private variables?

To understand what I mean by "expose" I meant having it show up in the inspector like this:

example of exposed methods

The reason I want this functionality is because I just encountered a bug where I was calling a public method from a script that was just meant to be called as a callback to the OnClick method. Pretty much the same reasoning as wanting to use [SerializeField] to hide variables but expose them to the inspector.

\$\endgroup\$
  • 1
    \$\begingroup\$ How do you expose any method to the editor? \$\endgroup\$ – Gnemlock Dec 21 '17 at 1:59
  • 3
    \$\begingroup\$ Also, have you tried making a public method that calls the private method? Not sure if applicable, as I can't work out what your actually trying to do. \$\endgroup\$ – Gnemlock Dec 21 '17 at 2:01
  • \$\begingroup\$ @Gnemlock Perhaps it was poorly explained, please see my edit for an image example and the reasoning behind wanting this functionality. \$\endgroup\$ – Charanor Dec 21 '17 at 2:16
  • \$\begingroup\$ You can't expose a private method to the editor. You want to have another script invoke it, and the only way to do that (and in fact the only thing that even makes sense) is for that method to be public. \$\endgroup\$ – Draco18s Dec 21 '17 at 3:39
  • \$\begingroup\$ @Draco18s I don't see how that's the only thing that makes sense - we can expose private variables to the editor (and other scripts!) so why not methods? \$\endgroup\$ – Charanor Dec 21 '17 at 4:04
4
\$\begingroup\$

You can't

Private methods can't be exposed to the editor. Your goal her is to have another script invoke the method, and the only way to do that (and in fact the only thing that even makes sense) is for that method to be public.

Technically speaking, you could invoke private methods via Reflection, but this is a performance nightmare and is not recommended. As such, the Unity Editor doesn't allow private methods to be the target for invocation in this manner.

Alternatively, you can allow buttons to invoke private methods by assining the Onclick behavior via code:

Button btn = someGO.GetComponent<Button>();
btn.onClick.addListener(delegate { myPrivateFunciton(); });

The scope that this code is running it has access to myPrivateFunciton and therefore has the executive authority to pass off a reference to another object (in this case, the button), similar to passing a private field reference to another class instance.

\$\endgroup\$
1
\$\begingroup\$

This topic seems to be very old, but since I stumbled upon the same issue and found this topic, here is my solution.

In nutshell, for every Unity component that exposes "Unity-only" methods you make two classes:

  1. Main class. A well designed public class that follows all the best practices. It declares Unity specific methods (like callbacks for the controls) as protected. The external consumers of your code will see this class and will only be able to use what you've explicitly declared public. They will be able to inherit and access the protected methods, but it would also require them to replace the component. And given the difficulty of dealing with the serialized Unity fields, I'd say it's not feasible. So, we may safely assume nobody will be calling your protected methods.
  2. Decorator class. A private protected sealed class that inherits from the main class and declares new public methods for what was declared as protected in the main class. The external consumers won't be able to see this class, but Unity will be able to see this class and invoke its callbacks!

The downside of this approach is that you cannot declare the main class sealed. But it's tolerable. Not that many people use it anyway.

Simple code sample:

// This class implements the logic.
public class MainClass : MonoBehaviour {
  protected void MyUnityCallback() {
    // External callers won't be able to use this method.
  }
}

// This class exposes methods to Unity.
sealed class MainClassDecorator : MainClass {
  public new void MyUnityCallback() {
    base.MyUnityCallback();
  }
}

HINT. Using "debug" mode in Unity inspector you can easily replace the main class by its decorator.

HINT2. To enable debug mode in the editor, right click on the inspector tab title.

\$\endgroup\$
-2
\$\begingroup\$

You seem to have a misconception about how accessibility-levels work in C# (or programming in general). An element marked private cannot be accessed outside of the class it's declared in, by definition.

The reason you are able to expose fields is because the editor is not working with the actual run-time instance, but rather with a serialized copy that the editor will substitute with an instance when the time is right. Fields are serializable, and serialized types that have a Drawer show up in the inspector, so when you override Unity's default behavior using the [SerializeField], and the protected/internal/private field gets serialized, it will show up in the inspector. It's more of a consequence of how the system works, rather than actually "exposing a private field" as if it was a concrete action that could be taken.

Methods are not serializable. Methods aren't data; they are procedures. It makes no sense to serialize a definition of "how to do something". Definitions are the responsibility of the classes/assembly. --- This is also the reason why you can't deserialize data without access to the assemblies that define it. The compiler is not clairvoyant; it needs to know the format that the data is supposed to be in, in order to reassemble the data back into a concrete instance.

Since methods aren't serializable, the inspector can only know about them through reflection. And while there are ways to get references to non-public methods through reflection, it will not be something that Unity will provide out-of-the-box, because it's something that presents many problems:

  • It breaks encapsulation.
  • It breaks the strictness principle.
  • It would lead to massive clutter.
    • The method-lists that Unity displays only show public methods; there are hundreds of non-public classes, and possibly thousands of non-public methods being omitted. The editor's reflection can't differentiate between the method you want to expose, and all other non-public stuff; it would have to show them all, and that would make the system unusable.
\$\endgroup\$
  • 1
    \$\begingroup\$ Your first paragraph is missleading, you can access private elements through reflection (as you noted yourself). This goes for changing the value of a readonly variable as well. Those keywords are basically only compiler flags telling the compiler "Hey, you shouldn't be able to access this element outside of this scope!". Because of this I assumed that [SerializeField] used reflection but obviously (if I had thought about the name...) I was wrong. -- There was actually a question today on SO about how the C# compiler handles private static readonly variables that is relevant here. \$\endgroup\$ – Charanor Dec 21 '17 at 19:42
  • \$\begingroup\$ @Charanor readonly has to do with (re-)writing values, not access. It tells the compiler "Hey, you see this value here? I don't care who can access it, but don't let anyone change it after it has been set for the first time!", which is completely different to "Hey, you see this room [private]? Only VIP members of the club can access it! That other room [internal] is only for members of this specific branch of the club! The other [protected] can be accessed by any member of any clubs in the brand! And the hall [public] is for everyone!". Completely different subjects. \$\endgroup\$ – XenoRo Dec 22 '17 at 0:51
  • \$\begingroup\$ My first paragraph is not misleading. You misunderstand the context of "cannot". It's not saying that it's absolutely impossible to access; it's saying that it cannot be done, through the normal programming paradigm. This is why reflection is mentioned, and acknowledged, as being able to do so, but as a brute-force method, that for this use-case would break several basic rules and principles of programming, and shouldn't be used. --- Reflection is not a valid solution here. And shouldn't be portrayed as such. \$\endgroup\$ – XenoRo Dec 22 '17 at 0:56
  • \$\begingroup\$ I was inaccurate in my sentence structure of readonly. My paragraph of course only applies to private :). But all of this aside I understand the implications of public, private, protected, static, etc. perfectly fine, and that was not why I was confused. I just reasoned that since it's possible to access private members Unity might have used reflection just to ease the development process. Not only that but I've read that Unity uses a modified version of C# (Mono?) and they may have implemented a more "native" way to do this. I'm new to Unity, so what do I know :) \$\endgroup\$ – Charanor Dec 22 '17 at 1:13
  • \$\begingroup\$ You seem to still have a misconception about reflection. Reflection does not give you "access" to the fields/methods; not in the same sense of access modifiers. Try to think about why reflection is considerably more expensive than the normal access paradigm. Consider the previous "club" analogy: Access modifiers are like tiered keycards that give access to different rooms. Reflection is different; it's like a mafia-boss that storms into the club armed with a loaded AK-47, takes the manager hostage, and forces his way thru whatever door he wants, but causes significant disruption too. \$\endgroup\$ – XenoRo Dec 22 '17 at 14:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.