Thread: how do Nullable<T> Type variables work?

  1. #1
    Registered User
    Join Date
    Sep 2011
    Posts
    71

    how do Nullable<T> Type variables work?

    I understand that the Nullable type is a struct whose identifier (either Nullable<T> or T?) be used to declare value type variables that can be assigned a null reference, which is something that normally cannot be done with value type variables, since, at least if I am not mistaken, by definition a value type variable always represents the same memory address and that memory address is always going to have a value of the variable's type, even if it's the default value of the variable.

    Reference types, on the other hand, and if I am not mistaking, can represent different memory addresses, as long as the memory addresses contain values of the same type as the reference type variable; by default, if I am not mistaken, and yes, I realize that I could be wrong about this part, they represent the memory address that contains the null reference, which is the only value that can be represented by the null type.

    Now, back to Nullable type variables, given that they are value type variables, how does their internal memory address representation mechanism differ from that of a typical value type variable and how is it similar to that of a reference type variable? behind the scenes, is it just a value type variable like all others except that it has the privilege of being able to represent, by default, the memory address that contains the null reference, or is it just a value type variable, also like all other value type variables, except that the one memory address it always represents also can store the null reference?

    (I realize that I might be overly confused about the memory address(s) where the null reference is stored. I'm assuming that there is one and only one memory address per application domain and specially designated at run-time, that always contains the null reference, and that's the memory address that all reference type variables by default represent, even though the type of the value in the memory address is the null type and not the type of the reference type variable itself.)
    Last edited by y99q; 12-15-2011 at 09:53 AM.

  2. #2
    Registered User
    Join Date
    Oct 2006
    Posts
    3,445
    in .Net, you really don't need to care about memory addresses. an object certainly has a memory address, but it's pretty irrelevant, because the garbage collector may choose to move the object at any time. it's more important to realize that when you have a reference type object, your variable that represents it is a reference or a "handle" to that object. with a value type, your variable IS the object. many times you need to have a value type object that contains NO value at all, which is why nullable types are so valuable. it's especially important when dealing with data from a database. fields in a database can be null, or they may have a value. a standard int data type would be insufficient to represent a field that may contain an int value, but may also contain no value at all (null).

  3. #3
    Registered User
    Join Date
    Sep 2011
    Posts
    71
    Quote Originally Posted by Elkvis View Post
    in .Net, you really don't need to care about memory addresses. an object certainly has a memory address, but it's pretty irrelevant, because the garbage collector may choose to move the object at any time.
    It's only irrelevant if you don't care to understand the way things work.

    it's more important to realize that when you have a reference type object, your variable that represents it is a reference or a "handle" to that object. with a value type, your variable IS the object.
    a variable cannot be an object. a variable represents a memory address where an object is located.

    many times you need to have a value type object that contains NO value at all, which is why nullable types are so valuable. it's especially important when dealing with data from a database. fields in a database can be null, or they may have a value. a standard int data type would be insufficient to represent a field that may contain an int value, but may also contain no value at all (null).
    which is true - and I thank you for taking the time to explain all this stuff - but your explanation doesn't answer my question nor tell me anything new.

  4. #4
    the hat of redundancy hat nvoigt's Avatar
    Join Date
    Aug 2001
    Location
    Hannover, Germany
    Posts
    3,130
    Now, back to Nullable type variables, given that they are value type variables, how does their internal memory address representation mechanism differ from that of a typical value type variable and how is it similar to that of a reference type variable?
    It doesn't. Simple as that. There is no magic. You could write it yourself and many people did write their own NullableInt, NullableDouble and so on until Generics were introduced.

    Untested example:

    Code:
    struct NullableInt
    {
    private int theValue;
    public int Value { get { if(!HasValue) throw new Exception(); return theValue; } set { HasValue = true; theValue = value;  } };
    public bool HasValue { get; private set; }
    public void SetNull() { HasValue = false; }
    }
    hth
    -nv

    She was so Blonde, she spent 20 minutes looking at the orange juice can because it said "Concentrate."

    When in doubt, read the FAQ.
    Then ask a smart question.

  5. #5
    Registered User
    Join Date
    Sep 2011
    Posts
    71
    Quote Originally Posted by nvoigt View Post
    It doesn't. Simple as that. There is no magic. You could write it yourself and many people did write their own NullableInt, NullableDouble and so on until Generics were introduced.
    In other words, if I were to implement my own generic nullable type. (untested example below)

    Code:
    class P
        {
            struct S<T> where T : struct
            {
                private T the_value;
    
                public S(T t)
                {
                    this = new S<T>();
                    this.HiddenValue = t;
                }
    
                public object HiddenValue
                {
                    set
                    {
                        if (value != null)
                        {
                            if (!(value is T)) throw new InvalidCastException();
                            Value = (T)value; HasValue = true;
                        }
                        else HasValue = false;
                    }
    
                    get { if (HasValue) return Value; return null; }
                }
                public T Value
                {
                    private set { the_value = value; }
                    get { if (HasValue) return the_value; throw new InvalidOperationException(); }
                }
                public bool HasValue { private set; get; }
    
                public static implicit operator S<T>(T t) { return new S<T>(t); }
                public static explicit operator T(S<T> t) { return t.Value; }
    
                public override string ToString()
                {
                    if (HasValue) return this.Value.ToString();
                    return "";
                }
            }
    
            static void Main(string[] args)
            {
    
                S<int> s = 6;
    
                Console.WriteLine(s);
                Console.WriteLine(s = 3);
                Console.WriteLine(s.HiddenValue = null);
    
                }
    }
    Then the main way it is different from the built-in Nullable<T> type is that the compiler allows you to assign values to a "hidden" property of the Object type without having to access it using the dot operator. in other words, in my code I'd do s.HiddenValue = 3 or s.HiddenValue = null, but in Visual C# the compiler allows you to get away with s = 3 and s = null, even though, in reality, you are not assigning to the instance of the struct itself, just to a 'hidden' property.

    Is that more or less the way it works?
    Last edited by y99q; 12-17-2011 at 11:37 AM.

  6. #6
    the hat of redundancy hat nvoigt's Avatar
    Join Date
    Aug 2001
    Location
    Hannover, Germany
    Posts
    3,130
    I'm not sure that anything about nullable is "built in". Your code looks fine, try to test it and see if the assignment works. My guess would be that the implicit cast will be used. No magic.
    hth
    -nv

    She was so Blonde, she spent 20 minutes looking at the orange juice can because it said "Concentrate."

    When in doubt, read the FAQ.
    Then ask a smart question.

  7. #7
    Registered User
    Join Date
    Sep 2011
    Posts
    71
    Quote Originally Posted by nvoigt View Post
    I'm not sure that anything about nullable is "built in". Your code looks fine, try to test it and see if the assignment works. My guess would be that the implicit cast will be used. No magic.

    we could have
    Code:
    struct S<T>
    {
    
    public static implicit operator S<T>(T t) { if (t != null) return new S<T>((T)t); return new S<T>(); }
    }
    
                S<object> s = 6;
    
                Console.WriteLine(s);
                Console.WriteLine(s = 3);
                Console.WriteLine(s = null); //code won't compile
                Console.WriteLine(s = 5);
    but the above will require that the type parameter T not be a value type. otherwise, the above implicit operator will not allow the code to compile since a value type cannot represent null.

    This also wouldn't compile because it is not possible to define a cast operator that converts from the object type to a derived type:

    Code:
    public static implicit operator S<T>(Object t) { if (t != null) return new S<T>((T)t); return new S<T>(); }
    Does it mean that the problem is handled is by providing a "placeholder" class?

    Code:
            class PlaceHolder{}
    
            struct S<T> where T : struct
            {
                private T the_value;
    
                public S(T t)
                {
                    this = new S<T>();
                    this.HasValue = true;
                    this.Value = t;
               }
    
                public T Value
                {
                    private set { the_value = value; }
                    get {if (HasValue) return the_value; throw new InvalidOperationException(); }
                }
    
                public bool HasValue { private set; get; }
                
                public static implicit operator S<T>(PlaceHolder t) { if(t == null) return new S<T>(); throw new InvalidCastException();}
                public static implicit operator S<T>(T t) { return new S<T>(t);}
                public static explicit operator T(S<T> t) { return t.Value; }
    
                public override string ToString()
                {
                   if (HasValue) return this.Value.ToString();
                   return "";
                }
            }
    
        class P
        {
            static void Main(string[] args)
            {
    
                S<int> s = null;
                
                Console.WriteLine(s = 7);
                Console.WriteLine(s = 3);
                Console.WriteLine(s = null);
                Console.WriteLine(s = 5);
    
    
                Console.Read();
            }
        }
    If that's the way it's done, it's very interesting. Who would have thought that by assigning null to a nullable struct all we were doing was implicitly converting from an unknown reference type to a default instance of the nullable type?

    Assuming that I'm on the right track, I still have one question,

    The default value of my struct is not null. The default value of a Nullable struct is null. Is this more built-in special functionality? Or is it just that .NET code, unlike application code, can actually define default unary operators?

    Code:
                int? i = default(Nullable<int>); //i = null
                S<int> j = default(S<int>); // j = default instance of S<int>
    Last edited by y99q; 12-18-2011 at 12:00 PM.

  8. #8
    Carnivore ('-'v) Hunter2's Avatar
    Join Date
    May 2002
    Posts
    2,879
    There's a nice article about it here, posted Feb. this year:
    Nullable&lt;T&gt; vs null

    It looks like the C# compiler does special-case this type.
    Just Google It. √

    (\ /)
    ( . .)
    c(")(") This is bunny. Copy and paste bunny into your signature to help him gain world domination.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. <type> far variables
    By ThLstN in forum C Programming
    Replies: 4
    Last Post: 03-27-2011, 01:28 PM
  2. How to find type of variables?
    By Niels_M in forum C Programming
    Replies: 1
    Last Post: 08-06-2010, 06:41 AM
  3. passing nullable type to a P/invoked function
    By WDT in forum C# Programming
    Replies: 3
    Last Post: 06-27-2009, 08:48 AM
  4. Replies: 9
    Last Post: 04-12-2009, 06:14 PM
  5. user input type variables
    By pastitprogram in forum C++ Programming
    Replies: 1
    Last Post: 09-05-2008, 07:21 AM