.NET Framework Developer Center > .NET Development Forums > .NET Base Class Library > Problem/Bug with the Generic List<T> class & nullable types
Ask a questionAsk a question
 

AnswerProblem/Bug with the Generic List<T> class & nullable types

  • Tuesday, May 22, 2007 10:44 AMJames Miles Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    A college of mine recently ran into this...

     

    Code Snippet

    List<Guid?> myGenericList = new List<Guid?>();

    IList myList = myGenericList;

    // This works.

    myGenericList.Add(new Guid?());

    // This doesn't work.

    myList.Add(new Guid?());

      

    The value "" is not of type "System.Nullable`1[System.Guid]" and cannot be used in this generic collection.

     

    System.ArgumentException was unhandled
      Message="The value \"\" is not of type \"System.Nullable`1[System.Guid]\" and cannot be used in this generic collection.\r\nParameter name: value"
      Source="mscorlib"
      ParamName="value"
      StackTrace:
           at System.ThrowHelper.ThrowWrongValueTypeArgumentException(Object value, Type targetType)
           at System.Collections.Generic.List`1.VerifyValueType(Object value)
           at System.Collections.Generic.List`1.System.Collections.IList.Add(Object item)
           at ConsoleApplication3.Program.Main(String[] args) in C:\source\Blue\Hemisphere\WinUI\Suppliers\ConsoleApplication3\Program.cs:line 18
           at System.AppDomain.nExecuteAssembly(Assembly assembly, String[] args)
           at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
           at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
           at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
           at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
           at System.Threading.ThreadHelper.ThreadStart()

Answers

  • Tuesday, May 22, 2007 1:11 PMSoe Moe Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    The problem is because of Nullable<T> structure.

    In int IList.Add(object item) method of the List<T> Generic class  which is explicit Interface Implementation,
    it uses IsCompatibleObject(object value) method (the one James Miles mentions) to verify value type.
    You can browse with .net Reflector.

    But IList.Add method accept object param, so boxing and unboxing is needed.
    The IsCompatibleObject(object value) method works well with the value types (except nullable<T>), e.g. int, decimal, and reference types, e.g. string.
    When the value type(int) converts to object type, the value of object is 0 (default value of int). That is not null value.
    But in your case, new Guid?() is null value and also ValueType (bcoz of nullable<T> struct). When it coverts to object type, the value of object is null value and also ValueType.
    Therefore, IsCompatibleObject(object value) method return false and throw exception.

    In your case, you can use IList<Guid?> (for myList) or just use List<Guid> (for myGenericList). I normally use List<Guid> and use Guid.Empty to verify it's null.

All Replies

  • Tuesday, May 22, 2007 10:49 AMJames Miles Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    from List<T> decompilation

     

    Code Snippet
    private static bool IsCompatibleObject(object value)
    {
    if (!(value is T) && ((value != null) || typeof(T).IsValueType))
    {
            return false;
        }
    return true;
    }

     

     

  • Tuesday, May 22, 2007 1:11 PMSoe Moe Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    The problem is because of Nullable<T> structure.

    In int IList.Add(object item) method of the List<T> Generic class  which is explicit Interface Implementation,
    it uses IsCompatibleObject(object value) method (the one James Miles mentions) to verify value type.
    You can browse with .net Reflector.

    But IList.Add method accept object param, so boxing and unboxing is needed.
    The IsCompatibleObject(object value) method works well with the value types (except nullable<T>), e.g. int, decimal, and reference types, e.g. string.
    When the value type(int) converts to object type, the value of object is 0 (default value of int). That is not null value.
    But in your case, new Guid?() is null value and also ValueType (bcoz of nullable<T> struct). When it coverts to object type, the value of object is null value and also ValueType.
    Therefore, IsCompatibleObject(object value) method return false and throw exception.

    In your case, you can use IList<Guid?> (for myList) or just use List<Guid> (for myGenericList). I normally use List<Guid> and use Guid.Empty to verify it's null.

  • Tuesday, May 22, 2007 4:17 PMOmegaManMVP, ModeratorUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Long story short, the others have touched on the bullet items, but you are pulling two different worlds together, Generics and collections. Though they share the same root namespace, there are differences to overcome and this is one.

    The IList is from the top level collections namespace and works with non generic objects. To quote MSDN on IList

    Represents a non-generic collection of objects that can be individually accessed by index.

    Since the code is in a sense going from .Net 1 to .Net 2 in methodology (Generics were released in .Net 2) to mix and match will require extra coding on your part. I recommend that you keep generics with generics and collections with objects.

  • Wednesday, May 23, 2007 3:58 AMJames Miles Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    >but you are pulling two different worlds together

     

    Microsoft framework developers intended it to work like this otherwise List would not implement the following interfaces.

     

    Code Snippet
    ...
    }

     

    >but you are pulling two different worlds together, Generics and collections.

     

    Actually, three different worlds are being pulled together. Collections, Generics & Nullable Types. Generics and Collections work fine together until Nullable Types are introduced.

     

    Code Snippet

    List<Guid> myGenericList = new List<Guid>();

    IList myList = myGenericList;

    // This works.

    myGenericList.Add(new Guid());

    // This works.

    myList.Add(new Guid());

     

  • Wednesday, May 23, 2007 8:05 AMnobugzMVP, ModeratorUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Soe has the correct answer. IList.Add(object) causes the Guid? to be boxed when can produce two outcomes, a null or a boxed Guid. That boxed value is now assignment compatible but not type compatible with the List<> element.  This issue is highly specific to a nullable type, using IList with a normal reference type will not be an issue.
  • Wednesday, May 23, 2007 2:46 PMOmegaManMVP, ModeratorUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Points well taken Miles. Thanks for the clarifications.