Default (T) с пустой коллекцией вместо null

Я хотел бы иметь общий метод, который возвращает значение по умолчанию для пройденного типа, но для типа коллекции я хотел бы получить пустую коллекцию вместо null, например:

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

Метод, который я начал писать, следующий:

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

Как это сделать?

EDIT: Если кто-то захочет получить значение по умолчанию какого-либо типа, основанное на экземпляре типа вместо идентификатора типа, эту конструкцию ниже можно использовать, то есть:

typeof(int[]).GetDefault();

Реализация внутренне основана на ответе @280Z28:

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);
    }
}

Ответ 1

Вы можете использовать магию статического конструктора, чтобы сделать это эффективно. Чтобы использовать значение по умолчанию в коде, просто используйте Default<T>.Value. Значение будет оцениваться только для любого заданного типа T один раз на время вашего приложения.

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;
        }
    }
}

Ответ 2

IList<object> не является типом коллекции, это интерфейс. Есть десятки возможных классов, которые вы могли бы вернуть.

Если вы передадите фактический тип коллекции, вы можете сделать это:

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

GetDefault<List<object>>();

Чтобы обрабатывать как пустые коллекции, так и null значения типов без конструкторов по умолчанию, вы можете сделать это следующим образом:

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);   
}