Why cannot convert null to type parameter T in c#?

2020-03-24 11:26发布

问题:

I'm converting a bunch of code from VB to C# and I'm running in to an issue with a method. This VB method works great:

Public Function FindItem(ByVal p_propertyName As String, ByVal p_value As Object) As T

    Dim index As Int32

    index = FindIndex(p_propertyName, p_value)

    If index >= 0 Then
        Return Me(index)
    End If

    Return Nothing

End Function

It allow the return of Nothing(null) for T.

The C# equivalent does not work:

public T FindItem(string p_propertyName, object p_value)
{
  Int32 index = FindIndex(p_propertyName, p_value);

  if (index >= 0) {
    return this[index];
  }
  return null;
}

It won't compile with this error:

The type 'T' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'System.Nullable<T>'

I need to be able to have the same functionality or it will break a lot of code. What am I missing?

回答1:

Since T can be either reference type or value type, so returning null will not satisfy if T is value type. You should return:

return default(T);

From the link default keyword:

Given a variable t of a parameterized type T, the statement t = null is only valid if T is a reference type and t = 0 will only work for numeric value types but not for structs. The solution is to use the default keyword, which will return null for reference types and zero for numeric value types. For structs, it will return each member of the struct initialized to zero or null depending on whether they are value or reference types. For nullable value types, default returns a System.Nullable, which is initialized like any struct.



回答2:

In case of C# you have to use default(T) and it will return 0 not Nothing or Null if you just return T and not T?.

Now In case of VB.net as per your example you say that you return Nothing which is correct but did you try to check return value of that function.

Like

Dim c = <<your class object>>.FindItem("test",Nothing);  // you have to pass valid values.

If you check value of C then it is 0 and not nothing.

Now comes to what happen at MSIL level.

When you compile you VB.net code and check its MSIL. you will find initobj !T and this is instruction for default(T). It is doing this automatically. So if you call your VB.net library from C# then also it will work.



回答3:

You can used this method

public static IList<int> ToIntList(this object values)
        {
            //try to convert using modified ChangeType which handles nullables
            try
            {
                if (values == null) return new List<int>();

                var charValues = values.ToType<string>();
                return charValues.Split(',').Select(n => Convert.ToInt32(n)).ToList();
            }
            //if there is an exception then get the default value of parameterized type T
            catch (Exception)
            {
                return new List<int>();
            }
        }

internal static object ChangeType(object value, Type conversionType)
        {
            if (conversionType == null)
            {
                throw new ArgumentNullException("conversionType");
            } 

            if (conversionType.IsGenericType && conversionType.GetGenericTypeDefinition() == typeof (Nullable<>))
            {
                if (value == null)
                {
                    return null;
                } 
                var nullableConverter = new NullableConverter(conversionType);
                conversionType = nullableConverter.UnderlyingType;
            }
            return Convert.ChangeType(value, conversionType);
        }

Used like this

userVar.ToType<int>()
userVar.ToType<string>()