jwaliszko
jwaliszko

Reputation: 17074

default(T) with empty collection instead of null

I'd like to have generic method which returns default value for passed type, but for collection type I'd like to get empty collection instead of null, for example:

GetDefault<int[]>(); // returns empty array of int's
GetDefault<int>(); // returns 0
GetDefault<object>(); // returns null
GetDefault<IList<object>>(); // returns empty list of objects

The method I started to write is following:

public static T GetDefault<T>()
{
   var type = typeof(T);
   if(type.GetInterface("IEnumerable") != null))
   {
      //return empty collection
   }
   return default(T);   
}

How to complete it ?

EDIT: If anyone would like get default value of some type, based on type instance instead of type identifier, this construction below can be used, i.e.:

typeof(int[]).GetDefault();

The implementation is internally based on @280Z28 answer:

public static class TypeExtensions
{
    public static object GetDefault(this Type t)
    {
        var type = typeof(Default<>).MakeGenericType(t);
        var property = type.GetProperty("Value", BindingFlags.Static | BindingFlags.Public);
        var getaccessor = property.GetGetMethod();
        return getaccessor.Invoke(null, null);
    }
}

Upvotes: 19

Views: 17499

Answers (3)

maxence51
maxence51

Reputation: 1054

Since C# 9, you can ommit type name with constructor. Maybe this is useful in your case.

int[] array = new();
var list = MyList?.Select(o => o.Id).ToList() ?? new ();

Upvotes: 0

Sam Harwell
Sam Harwell

Reputation: 99999

You can use the magic of a static constructor to do this efficiently. To use the default value in code, simply use Default<T>.Value. The value will only be evaluated for any given type T once for the duration of your application.

public static class Default<T>
{
    private static readonly T _value;

    static Default()
    {
        if (typeof(T).IsArray)
        {
            if (typeof(T).GetArrayRank() > 1)
                _value = (T)(object)Array.CreateInstance(typeof(T).GetElementType(), new int[typeof(T).GetArrayRank()]);
            else
                _value = (T)(object)Array.CreateInstance(typeof(T).GetElementType(), 0);
            return;
        }

        if (typeof(T) == typeof(string))
        {
            // string is IEnumerable<char>, but don't want to treat it like a collection
            _value = default(T);
            return;
        }

        if (typeof(IEnumerable).IsAssignableFrom(typeof(T)))
        {
            // check if an empty array is an instance of T
            if (typeof(T).IsAssignableFrom(typeof(object[])))
            {
                _value = (T)(object)new object[0];
                return;
            }

            if (typeof(T).IsGenericType && typeof(T).GetGenericArguments().Length == 1)
            {
                Type elementType = typeof(T).GetGenericArguments()[0];
                if (typeof(T).IsAssignableFrom(elementType.MakeArrayType()))
                {
                    _value = (T)(object)Array.CreateInstance(elementType, 0);
                    return;
                }
            }

            throw new NotImplementedException("No default value is implemented for type " + typeof(T).FullName);
        }

        _value = default(T);
    }

    public static T Value
    {
        get
        {
            return _value;
        }
    }
}

Upvotes: 14

Ben Voigt
Ben Voigt

Reputation: 283883

IList<object> is not a collection type, it's an interface. There are dozens of possible classes you could return.

If you pass in an actual collection type, you can do this:

public static T GetDefault<T>() where T : new
{
   if (typeof(IEnumerable).IsAssignableFrom(typeof(T)))
   {
      return new T();
   }
   return default(T);   
}

GetDefault<List<object>>();

To handle both empty collections, and null values of types without default constructors, you can do it like this:

public static T GetDefault<T>()
{
   if (typeof(IEnumerable).IsAssignableFrom(typeof(T)))
   {
            if (typeof(T).IsGenericType)
            {
                Type T_template = typeof(T).GetGenericTypeDefinition();
                if (T_template == typeof(IEnumerable<>))
                {
                    return (T)Activator.CreateInstance(typeof(Enumerable).MakeGenericType(typeof(T).GetGenericArguments()));
                }
                if (T_template == typeof(IList<>))
                {
                    return (T)Activator.CreateInstance(typeof(List<>).MakeGenericType(typeof(T).GetGenericArguments()));
                }
            }

      try {
          return Activator.CreateInstance<T>();
      }
      catch (MissingMethodException) {} // no default exists for this type, fall-through to returning null
   }
   return default(T);   
}

Upvotes: 11

Related Questions