2011-12-31

C# Blooper №1: No warnings about uninitialized readonly members when the class is public and the member is public, protected or protected internal.


Before reading any further, please read the disclaimer.

The C# compiler is kind enough to give you a "field is never assigned to" warning if you forget to initialize a readonly member which is private or internal, or if the class in which it is being declared is internal. But if the class is public, and the readonly member is public, protected or protected internal, then no warning for you! Why, oh why?

namespace Test1  
{  
    class Test1 
    {  
#if TRY_IT  
        public readonly int m; //OK: warning CS0649: Field is never assigned to, and will always have its default value 0  
        protected readonly int n; //OK: warning CS0649: Field is never assigned to, and will always have its default value 0  
        internal readonly int o; //OK: warning CS0649: Field is never assigned to, and will always have its default value 0  
        private readonly int p; //OK: warning CS0649: Field is never assigned to, and will always have its default value 0  
        protected internal readonly int q; //OK: warning CS0649: Field is never assigned to, and will always have its default value 0  
         
        Test1() 
        { 
            if( p != 0 ) //To avoid warning 'The field is never used' 
                return; 
        } 
#endif 
    }  
  
    public class Test2 
    {  
#if TRY_IT  
        private readonly int m; //OK: warning CS0649: Field is never assigned to, and will always have its default value 0  
        internal readonly int n; //OK: warning CS0649: Field is never assigned to, and will always have its default value 0  
 
        Test2() 
        { 
            if( m != 0 ) //To avoid warning 'The field is never used' 
                return; 
        } 
#endif  
        public readonly int o; //Blooper: no warning about field never assigned to.  
        protected readonly int p; //Blooper: no warning about field never assigned to.  
        protected internal readonly int q; //Blooper: no warning about field never assigned to. 
    }  
  
    public sealed class Test3 
    {  
        public readonly int m; //Blooper: no warning about field never assigned to.  
    }  
}  

For a moment you might think "well, a descendant might initialize that member", but that theory does not hold any water, for a number of reasons:
  • Internal classes may also be subclassed, but the compiler does not fail to issue the warning in their case.
  • Sealed classes may not be subclassed, but the compiler fails to issue the warning in their case, as Test3 in the sample code demonstrates.
  • The warning makes sense for the sake of the integrity of the base class regardless of what a derived class may or may not do.
  • Lastly but most importantly, the C# specification expressly prohibits a derived class from initializing a readonly member of a base class. You get Error CS0191: A readonly field cannot be assigned to (except in a constructor or a variable initializer) which, incidentally, is a little bit misleading, because you may be trying to assign the field from within a constructor, only it is the constructor of the wrong class.
According to MSDN Documentation about this warning, the exhibited behavior is to be expected:
Compiler Warning (level 4) CS0649:
Field 'field' is never assigned to, and will always have its default value 'value'
The compiler detected an uninitialized private or internal field declaration that is never assigned a value.
The question is: why?

UPDATE:

I posted this question on StackOverflow, and Eric Lippert himself answered it. The short answer is that it is an oversight of the compiler, but the long answer is also quite interesting and worth reading.
-

C# Bloopers


Please do not get me wrong; C# is awesome. It is the language of my choice, even though I am pretty well versed in C++ and Java. That having been said, it cannot be denied that C# has its share of flaws, too. In this series of posts I am documenting some of them, in no particular order.

Also please note that many of the issues described herein are Visual C# bloopers, not C# bloopers in general.

C# Blooper №1: No warnings about uninitialized readonly members when the class is public and the member is public, protected or protected internal.

C# Blooper №2: No warnings about accessing uninitialized members.

C# Blooper №3: No warnings about fields having already been initialized.

C# Blooper №4: Lame/annoying variable scoping rules, Part 1

C# Blooper №5: Lame/annoying variable scoping rules, Part 2

C# Blooper №6: No warnings about unused parameters.

C# Blooper №7: No warnings about unused private methods.

C# Blooper №8: No warnings for conditions that are always true/false

C# Blooper №9: Annoying case statement fall-through rules.

C# Blooper №10: Switch statements are not properly formatted.

C# Blooper №11: Zero to Enum conversion weirdness

C# Blooper №12: 'Where' constraints not included in method signatures

C# Blooper №13: C# Blooper №13: Stack and Queue do not implement ICollection

C# Blooper №14: Weird / annoying interface method visibility rules.

Stay tuned, there is more to come.
-