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\$
  • 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\$ – LifGwaethrakindo Apr 7 at 3:47
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...).

| improve this answer | |
\$\endgroup\$
  • 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\$ – William Mariager Apr 7 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\$ – BlueRaja - Danny Pflughoeft Apr 7 at 18:36
  • 12
    \$\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\$ – Eric Lippert Apr 7 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\$ – William Mariager Apr 7 at 19:21
14
\$\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.

| improve this answer | |
\$\endgroup\$
  • 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\$ – Joe Sewell Apr 8 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\$ – Eric Lippert Apr 8 at 23:22

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.