Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

Any C# / OOP experts out there?

Discussion in 'Scripting' started by Flipbookee, Dec 20, 2015.

  1. Flipbookee

    Flipbookee

    Joined:
    Jun 2, 2012
    Posts:
    2,374
    Hey folks, is any of you brave enough to answer this tricky question without trying to compile the code example and, of course, without searching for the answer on the internet? :)

    The questions is "what's wrong with this code and why":
    Code (csharp):
    1. class A
    2. {
    3.     protected int n;
    4. }
    5.  
    6. class B : A
    7. {
    8.     public void Test()
    9.     {
    10.         A a = new A();
    11.         Debug.Log( a.n );
    12.        
    13.         B b = new B();
    14.         Debug.Log( b.n );
    15.        
    16.         C c = new C();
    17.         Debug.Log( c.n );
    18.     }
    19. }
    20.  
    21. class C : B
    22. {
    23. }
    To be honest, the correct answer was kind of surprising for me :p and then I asked a few people but no one knew, so I thought I'm going to ask you now ;)
     
    apsdsm and Ryiah like this.
  2. HonorableDaniel

    HonorableDaniel

    Joined:
    Feb 28, 2007
    Posts:
    2,799
    You're not using UnityEngine, Debug class is unavailable.
     
    BrienKing, Kiwasi, apsdsm and 2 others like this.
  3. Diericx

    Diericx

    Joined:
    Jun 8, 2013
    Posts:
    62
    Is it that c.n isn't accessible because n is protected and the class C doesn't directly derive from A?
     
  4. BenZed

    BenZed

    Joined:
    May 29, 2014
    Posts:
    524
    Yep. @_Daniel_ is right, too.
     
  5. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    788
    That's not quite right.

    The protected member n is only accessible from it's own class. B and C will both inherit this member but that doesn't mean they are allowed to touch the inherited member in other classes. In short: the method in B isn't allowed to touch A.n or C.n despite B inheriting the same variable with same name. That would technically be breaking encapsulation.
     
    Dustin-Horne and Kiwasi like this.
  6. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,431
    While the protection levels will cause an error, that's not what's wrong in this code. The architecture is flawed. A class has no business knowing about its derived class...

    ... I think. I'll be the first to admit that I don't have that strong a grasp on the more convoluted design patterns. Is there any legitimate use case for a class to know about its derived classes? Or code that generally looks like the OP?
     
  7. BrienKing

    BrienKing

    Joined:
    Oct 11, 2015
    Posts:
    32
    Protected Members are available throughout the entire inheritance hierarchy.
     
  8. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,431
    So I cheated and compiled it.

    Code (CSharp):
    1. error CS1540: Cannot access protected member `A.n' via a qualifier of type `A'. The qualifier must be of type `B' or derived from it
    There are no complaints on any of the other lines. So the only access that is illegal is accessing the protected member on an instance of the parent class. I did some further googling as I was curious.

    This is meant as a protection against accessing protected variables on classes that are A but are not B. Otherwise you could break encapsulation using polymorphism. Something like this:

    Code (csharp):
    1. class A {
    2.     protected string n;
    3. }
    4.  
    5. class B : A {
    6.     public void Test()
    7.     {
    8.         D d = new D();
    9.         A a = (A)D;
    10.         a.n = "I've just accessed a protected variable outside of my inheritance chain!";
    11.     }
    12. }
    13.  
    14. class D : A {}
    When you think about it it makes sense. B can access the protected members on any B. Including classes that are inherit from B. However there is no guarantee that any given A will actually be a B. So accessing protected variables on A is illegal.

    Edit: Which is pretty much what the docs say: https://msdn.microsoft.com/en-us/library/s9zta243.aspx
     
    Last edited: Dec 21, 2015
    eisenpony likes this.
  9. BrienKing

    BrienKing

    Joined:
    Oct 11, 2015
    Posts:
    32
    Ok, I just re-read this... and know what's wrong now.... :)

    You cannot do a.n, b.n, or c.n because they are protected. If you set "n" to protected internal, you could do that. You can only access "n" from within the derived class.

    You can do Debug.Log(this.n); but that will get you the current instance's version of n. Creating a new instance of A even in a derived class won't give you access to any thing that is protected.
     
    Last edited: Dec 21, 2015
  10. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    788
    I was wondering if that might be the case since upward casts are allowed implicitly. i.e.,
    Code (csharp):
    1. B c = new C();
    I wasn't sure though.

    Funny that access to A can't be assumed since there is no such thing as multiple inheritance. Since B doesn't explicitly have a variable explicitly called n, the compiler could reason that the protected variable is inherited from A. @BoredMormon Your point about accessing outside of the inheritance chain is reasonable though I think your first comment is more sane.
    In theory, B should be able to access A's protected variable without causing too much damage, provided the Liskov principle is followed and the two pieces of code are behaving in the same way. In my mind, even access to D's protected variable from B isn't out of the question assuming Liskov holds, because the two pieces of code should be making similar assumptions about the variable. However, since there is no guarantee that a programmer will follow those rules, it's better to enforce the encapsulation. Also no guarantee the code in C makes the same assumptions about n as B, so better still to avoid this situation entirely.

    I occasionally put references in my abstract root class as a factory method. Though I would never try what was in the OP. e.g.,
    Code (csharp):
    1. class A
    2. {
    3.   public static A Create(string name)
    4.   {
    5.     if (name == "bee")
    6.       return new B();
    7.     else if (name == "see")
    8.       return new C();
    9.     else
    10.       throw new NotImplementedException();
    11.   }
    12. }
    That's not right. internal marks a type or variable only accessible within the same module (i.e., assembly.) Since the class is already internal, there is no way this variable could be exposed to an external assembly so, in this case, internal would be redundant.
     
    Last edited: Dec 21, 2015
    Kiwasi likes this.
  11. BrienKing

    BrienKing

    Joined:
    Oct 11, 2015
    Posts:
    32
    Actually, it is 100% correct. I just tested it and it works just fine.

    Code (CSharp):
    1.     public class Class1
    2.     {
    3.         protected string protVar = "";
    4.         protected internal string protVar2 = "";
    5.  
    6.     }
    7.  
    8.     public class Class2 : Class1
    9.     {
    10.  
    11.         public void DoSomething()
    12.         {
    13.             Class1 tempClass = new Class1();
    14.  
    15.             tempClass.protVar2 = "This is allowed";
    16.         }
    17.  
    18.     }
    By putting Internal it means any class within the same assembly can access what ever it is (field, property, method). So to any classes within the Assembly the access is Public. For any classes outside of the assembly, it's protected and not accessible.
     
    HonorableDaniel likes this.
  12. Flipbookee

    Flipbookee

    Joined:
    Jun 2, 2012
    Posts:
    2,374
    Yeah, but that's not the point I wanted to illustrate ;)

    No, there's no difference in C# for protected members in directly or indirectly derived classes.

    That's getting closer, but it's wrong :p

    My question was only about accessing the protected member n within that particular context through instances of different classes in the inheritance chain. It's all about understanding the fundamental meaning of the "protected" level of accessibility, and this particular example has nothing to do with design patterns or code design except that in order to understand some of those you may also have to understand the basics of OOP and encapsulation, which is nicely shown in the example.

    Well, that's not quite right. This code will not compile.

    Boo, you ruined it :p hehe

    Yes, a.n is not accessible in that context because the instance referenced by a is not guarantied to be an instance of same class B or a derived class C. The protected modifier allows the derived classes to access the member through an instance of that derived class or a class derived from it, but not through an instance of the base class.

    With polymorphism this makes sense. The surprising part for me was that a.n IS NOT accessible and also that c.n actually IS accessible :eek:
     
    Dustin-Horne likes this.
  13. Prototypetheta

    Prototypetheta

    Joined:
    May 7, 2015
    Posts:
    119
    I thought c.n wasn't accessible either but couldn't explain why. I understand why a.n can't be accessed but I assumed c.n wouldn't be accessible either.
     
  14. shaderbytes

    shaderbytes

    Joined:
    Nov 11, 2010
    Posts:
    900
    whats wrong with this code is the non cuddled braces :p
     
    Flipbookee likes this.
  15. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    788
    Interesting! I didn't know that about protected internal members. I can't imagine when I would think protecting a member would be a good idea to external code but okay for my own code. The docs say it can be useful for component based architecture in which the components want easy access to work together.. I'll have to think about that some more. Thanks!

    I disagree -- like most "puzzles" this shows a rather obscure use of inheritance and non intuitive results of encapsulation. It's the kind of arcane rule we invented compiler error messages and intellisense for.

    Still, it was a fun puzzle so thanks for sharing!
     
    Flipbookee likes this.
  16. BrienKing

    BrienKing

    Joined:
    Oct 11, 2015
    Posts:
    32
    Well to qualify what I meant, "n" is accessible in any class the derives from A whether directly or indirectly. However, it's only accessible internally (this.n) and it's referencing that instance's "n". You were trying to access it externally (<different instance>.n) which is prohibited because it's marked as protected. If you had marked "n" as protected internal, then your code would have compiled no problem and you could have accessed externally because A B and C would have been created in the same assembly.
     
  17. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    788
    Yes,
    was in reply to
    If you weren't trying to do something strange, like access your parents protected members, then C would compile to have the protected members inherited through the chain from A.
     
  18. Flipbookee

    Flipbookee

    Joined:
    Jun 2, 2012
    Posts:
    2,374
    Just one last note about this example, the same accessibility rules of protected members stand in C++. So this is not a C# specific rule, but seems like a rule more related to OO and encapsulation.

    Hehe, that's not true either ;) there's no difference if am I accessing a protected member of the 'this' instance or another instance of that same type. The 'this' instance is only privileged from the syntactic point of view - there's no need to qualify n with a 'this.' but other than that the compiled code is identical.

    In a similar way it is allowed to access private members of the same type but on other instance than the 'this' instance!

    There's also a little known catch with internal protected members! In CIL there are two types of internal protected members: internal-and-protected and internal-or-protected, while in C# internal protected always compiles to internal-or-protected. This makes the member accessible from any type within the same assembly, and outside of the same assembly only classes derived from that class can access it, so it's like a protected member.
     
    Kiwasi likes this.
  19. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,562
    It actually makes zero sense to call A.n anyway. Because B is derived from A, so calling B.n is essentially the same thing as it's calling the protected member on the base class. The reason it's not accessible is because of the protection level. Calling A.n from inside of B is essentially the same thing as trying to call A.n from outside of the class hierarchy. It's restricted. B.n is allowed to call C.n because C is derived from B. In essence it's really the same thing, as by proxy you're still calling A.n through the inheritance chain. Realistically, you really shouldn't be able to call C.n from B either. But as stated before, B should never even know that C exists.

    Now, if you want to have a little more fun with it, mark the method as "virtual" as well. That gets fun because you can no longer guarantee that the behavior of B.n and/or C.n matches the behavior of A.n.
     
    Kiwasi likes this.
  20. Flipbookee

    Flipbookee

    Joined:
    Jun 2, 2012
    Posts:
    2,374
    @Dustin Horne, making the method virtual will not change anything regarding A.n! It may only change that at runtime an override method is called instead of the method B.Test(), but that will not alter the implementation of B.Test() or the protection level of A.n, right?
     
  21. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,562
    Right, it doesn't change the implementation or protection at all. It will in some cases generate compiler warnings though. Here's a good example of something that will:

    Code (csharp):
    1.  
    2. public abstract class A
    3. {
    4.     protected Guid _id;
    5.  
    6.     protected virtual Guid GenerateId()
    7.     {
    8.          return Guid.NewGuid();
    9.     }
    10.  
    11.     public void CreateId()
    12.     {
    13.          _id = GenerateId();
    14.     }
    15. }
    16.  
    The code above will actually generate compiler warnings because A is abstract and you can't guarantee that B or C will or will not override GenerateId and it may not do what you want. So, calling B.CreateId() and C.CreateId() may not do what you expect it to.

    The example above is non-sensical but I've actually run into it before, although I could safely ignore because it was my own code, but I was creating immutable classes with private constructors and using static helper methods to do some generation.
     
    Last edited: Dec 22, 2015
  22. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,562
    Edit: Fixing bad example

    Actually, I missed a step in my description above. The warning happens if you attempt to call that virtual method from a constructor. So for instance:

    Code (csharp):
    1.  
    2. public class A
    3. {
    4.     protected Guid _id;
    5.     protected bool _needsId = false;
    6.  
    7.     public A()
    8.     {
    9.          _id = GenerateId();
    10.     }
    11.  
    12.     protected virtual Guid GenerateId()
    13.     {
    14.           return  Guid.Empty;
    15.     }
    16.  
    17. }
    18.  
    19. public class B : A
    20. {
    21.     public B()
    22.     {
    23.           _needsId = true;
    24.     }
    25.  
    26.     protected virtual override Guid GenerateId()
    27.     {
    28.             return _needsId == true ? Guid.NewGuid : _id;
    29.     }
    30. }
    31.  
    The above code will actually not generate a new Id. The virtual method is called from the constructor of A, but B has not been constructed yet, so the _id variable will always be Guid.Empty. This will also generate a compiler warning because you shouldn't ever call virtual methods from a constructor.

    In the example above, it should also be noted that when constructor A calls GenerateId, it is actually calling B.GenerateId, but _needsId is still false. This is because all of the fields and members of the class hierarchy get generated first. This means that B's override of GenerateId is generated. However, after this is done, then it actually calls the constructor logic starting from base to most derived. This means that constructor A calls B.GenerateId, but constructor B hasn't been executed yet, so _needsId is still false and will result in the default value of _id just being returned.
     
    Last edited: Dec 22, 2015
    Flipbookee likes this.
  23. Flipbookee

    Flipbookee

    Joined:
    Jun 2, 2012
    Posts:
    2,374
    @Dustin Horne, nice! I've done something similar a few times :p Good that there's a warning to save us.
     
    Dustin-Horne likes this.
  24. BrienKing

    BrienKing

    Joined:
    Oct 11, 2015
    Posts:
    32
    Maybe we are talking two different things here....

    I was using "this" to clarify that I was referring to the current instances version of "n" and not "n" from a different instance.

    Code (CSharp):
    1. public class Class1
    2. {
    3.     protected int MyNumber = 0;
    4.     protected internal int MyOtherNumber = 0;
    5.     internal int ThirdNumber = 0;
    6. }
    7.  
    8. public class Class2 : Class1
    9. {
    10.     public void DoSomething()
    11.     {
    12.         Class1 class1 = new Class1();
    13.  
    14.         // These two lines are exactly the same, "this" is redundant and can be removed.
    15.         MyNumber = 10;
    16.         this.MyNumber = 10;
    17.  
    18.         class1.MyNumber = 10; // Not allowed and won't compile because "class1" is a different instance.
    19.         class1.MyOtherNumber = 20; // This is allowed because both classes are in the same assembly and MyOtherNumber is marked as "protected internal"
    20.     }
    21.  
    22. }
    23.  
    24. public class Class3
    25. {
    26.     public void DoSomething()
    27.     {
    28.         Class2 class2 = new Class2();
    29.  
    30.         class2.MyOtherNumber = 5;
    31.         class2.ThirdNumber = 6;// This is allowed because both classes are in the same assembly and ThirdNumber is marked as "internal"
    32.  
    33.         ((Class1)class2).MyOtherNumber = 7;  // Valid because class2 derives from class1
    34.         ((Class1)class2).ThirdNumber = 8; // Valid because class2 derives from class1
    35.  
    36.     }
    37. }
    Not sure what you are trying to say here... private members are only accessible from the class they defined in. If B inherits from A, B will never see A's private members.

    That is correct.
     
    Flipbookee likes this.
  25. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    788
    I think he's noting that an instance of B could see another B's private variable.

    Code (csharp):
    1. class B
    2. {
    3.   private int foo;
    4.   public int Compare(B other)
    5.   {
    6.     if (this.foo > other.foo)
    7.       return 1;
    8.     else if (this.foo < other.foo)
    9.       return -1;
    10.     else
    11.       return 0;
    12.   }
    13. }
    The confusion is stemming from terminology at this point. Everyone appears to know what's going on.
    BrienKing, I think you are using the word "accessible" to mean, the subclasses will have their own variable called n.
    Flipbookee, I think you are interpreting the word "accessible" to mean accessible from another class.
     
    Last edited: Dec 22, 2015
  26. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,431
    He is trying to say this code is legal.

    Code (CSharp):
    1. public A {
    2.     private string n;
    3.    
    4.     void SomeMethod (A someOtherA){
    5.         someOtherA.n = "This access is allowed";
    6.     }
    7. }
     
    Flipbookee likes this.
  27. BrienKing

    BrienKing

    Joined:
    Oct 11, 2015
    Posts:
    32
    That is interesting. That seems very counter-intuitive. Will have to research why that works.
     
    Flipbookee likes this.
  28. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    788
    My understanding is that encapsulation is provided by the class, not by the object. So it makes sense that two objects of the same class have access to all members, private or otherwise.

    The main purpose of encapsulation is to prevent code that is not familiar with the assumptions made by data or other code does not interact. Pieces of code in a single class need to make the same assumptions, otherwise you have bigger problems. This is the main reason we try to reduce the complexity of each class -- we don't want to risk making two sets of assumptions in the same piece of code.
     
    Kiwasi, Flipbookee and BrienKing like this.
  29. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,431
    There are some very obvious use cases for it. Factory type patterns is one. Cloning instances is another. Really any time A produces and configures a new instance of A. I'm sure there are other reasons too.

    Since we are talking on access modifiers in general the one piece C# seems to be missing is restricting access to a small subset. I would really love to have a 'internal to namespace' access modifier. Basically saying this grouping of classes can access each other through these methods. They can be accessed from outside the group with these other methods. There may be good reasons why this is not a thing.

    I could also get the same effect using separate assemblies and internal.
     
  30. Flipbookee

    Flipbookee

    Joined:
    Jun 2, 2012
    Posts:
    2,374
    @eisenpony is right! You can think of access level modifiers as they are always describing the class, not a particular instance of that class. You could do the same inside a static method for example, and the static method doesn't have a 'this' instance.

    Yeah, separate assemblies will help there. Friend types in C++ do exactly that, but that's probably not the best practice.
     
    Dustin-Horne likes this.
  31. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,431
    I may have to play around with separate assemblies. It seems very heavy handed for projects of the size I work on.

    At the moment I manage 'system encapsulation' by using a pen and paper spaghetti diagram. Its pretty manual work. It would be nice to have slightly finer control.
     
  32. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,562
    Of course you can still use reflection to access private / internal methods. :p
     
    Kiwasi and Flipbookee like this.
  33. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,431
    True. But if I'm going to go to that much effort then I deserve the result.
     
  34. Flipbookee

    Flipbookee

    Joined:
    Jun 2, 2012
    Posts:
    2,374
    Hehe, yes @Dustin Horne :)

    @BoredMormon, have you thought about nesting those related types in a static class, making the types private and their members public? That would probably not work for MonoBehaviour classes...
     
    Kiwasi likes this.
  35. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,431
    That's another interesting approach I could play around with.