11
\$\begingroup\$

I have this private coroutine in my WinDialogue script:

private IEnumerator ActivateStars(int count) { for (int i = 0; i < count; i++) { _stars[i].SetActive(true); yield return new WaitForSeconds(.3f); } } 

And somehow I’m still able to call it from my UIManager script:

_winDialogue.GetComponent<WinDialogue>().StartCoroutine("ActivateStars", starCount); 

How can that be if I marked it as private?

\$\endgroup\$
1
  • 6
    \$\begingroup\$ This is mere conjecture...But given that you are using the StartCoroutine that takes a string as an argument (instead of an IEnumerator, like the other overload), maybe it uses reflection (see: docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…) to get the IEnumerator, regardless of its accessibility (reflection lets you get private members and methods, most of the times passing an enum argument as BindingFlags.NonPublic. Note that using reflection is a little bit more expensive). If you are concerned by the accessibility, then use the other method override. \$\endgroup\$ Commented Apr 7, 2020 at 3:47

2 Answers 2

25
\$\begingroup\$

When you start a coroutine by providing the method name as a string, then the Unity engine uses reflection to find the method at runtime, which doesn't care about the visibility of methods. This is not unusual for Unity. You might have noticed that methods like Start and Update are declared private, yet the Unity engine invokes them from outside the class.

If you would prefer proper software engineering principles - like names being resolved at compile-time and visibility rules being respected - use the variant of StartCoroutine which takes an IEnumerator instead of a string to denote the method.

WinDialogue dialogue = _winDialogue.GetComponent<WinDialogue>() dialogue.StartCoroutine(dialogue.ActivateStars(starCount)); 

This will give you a compile-time error if WinDialogue.ActivateStars is private (or does not return an IEnumerator... or gets called with the wrong arguments... or is misspelled...).

\$\endgroup\$
4
  • 1
    \$\begingroup\$ Might be worth adding a comment that in C#, as long as Reflection is available, nothing is ever truly private. public/private/internal/etc are all just suggestions for other devs more than something that gets enforced. \$\endgroup\$ Commented Apr 7, 2020 at 15:38
  • 5
    \$\begingroup\$ @WilliamMariager: That's not entirely true. It is enforced by the runtime for partially-trusted code: docs.microsoft.com/en-us/dotnet/framework/… \$\endgroup\$ Commented Apr 7, 2020 at 18:36
  • 13
    \$\begingroup\$ @BlueRaja-DannyPflughoeft: The whole situation is absurdly complicated and I wish we had designed a simpler system in the original .NET code security model. The question of whether partial trust code gets private reflection is even more complicated than it seems; you can get into a situation where two assemblies that both lack ability to do private reflection on other assemblies can nevertheless reflect on each other's private members, which is surprising to say the least. \$\endgroup\$ Commented Apr 7, 2020 at 18:55
  • \$\begingroup\$ @BlueRaja-DannyPflughoeft In that case I wouldn't consider Reflection fully available, which is why I added that caveat. There are many ways to limit what can be done once you go into various security options in AppDomains. \$\endgroup\$ Commented Apr 7, 2020 at 19:21
13
\$\begingroup\$

The answer from Philipp is correct; I'd like to extend it slightly by addressing directly your statement:

somehow I’m still able to call it ... how can that be if I marked it as private?

The question indicates an important but subtle misunderstanding of what "private" applies to. "Private" does NOT mean "cannot be called from outside". Consider:

class C { private static void M() { whatever } public static Action A() { return M; } } 

Any code can call M() by invoking C.A()(). Notice that in this scenario the call to M is made from code outside C; there is no call to M() inside C, merely returning a delegate to it.

And as Philipp's answer notes, there are other ways that code can access C.M, such as having been granted sufficient permissions to do private reflection.

What then does private mean, if not "this code can only be called from inside C?"

private means the name of the thing declared may only be legally referred to by an identifier within the accessibility domain of a private member. That is, use of the identifier M to refer to the private member C.M is only legal inside the accessibility domain of C.M.

The private accessibility domain for a method of a class is the region of program text that falls within the outer { } braces for the class (including partial class declarations and nested classes).

The thing that I'm emphasizing here is that private gives a restriction on the source code of a legal program. It says that the actual program text M may only be used to refer to member C.M if that program text appears somewhere inside the braces of the class declaration. An attempt to use the program text M outside of the accessibility domain will result in either binding to a different thing named M, or an error.

But the key thing to understand here is that private is all about the legality of the text of the program's source code. It is not a fact about behaviour at runtime! You can still call a private member; you just can't do it by writing source code that uses identifier M outside of the declaration of C.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Just to clarify, the compiled assemblies do have corresponding accessibility modifiers, both for compilers to obey when source code references the assembly and for the runtime to throw exceptions when a call is made "directly" (this might occur if assembly A referenced a type/member in assembly B at compile time, but the version of B resolved at runtime has changed to prohibit this access). But Eric's point is that you can't count on private to guard your members from all kinds of access (reflection, indirection, etc.); it's not a security feature. \$\endgroup\$ Commented Apr 8, 2020 at 22:51
  • \$\begingroup\$ @JoeSewell: That's exactly right; thanks for that clarification. Indeed, IL which makes a call to a private method will be flagged as unverifiable, and unverifiable code requires full trust to execute, so it is in that sense a security feature. But you should not treat accessibility modifiers as security features. \$\endgroup\$ Commented Apr 8, 2020 at 23:22

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.