运行时类型的默认值

181

我想知道任何给定类型的默认值。

在C#中,有一个关键字叫做default可以用于获取它的默认值,就像这样:

object obj = default(Decimal);

但是我有一个 Type 实例(称为 myType),如果我这样说:

object obj = default(myType);

它不起作用

有没有什么好方法可以做到这一点? 我知道使用一个巨大的 switch 块可以工作,但那不是一个好的选择。


你能解释一下为什么它不起作用吗?是出现了错误吗?还是它只是没有返回你期望的结果? - Gabe
1
@gabe,它适用于类型名称,但不适用于该类型名称的Type实例,我的意思是default(Decimal)可以工作,但default(typeof(Decimal))不能。 - viky
6个回答

310

实际上只有两种可能性:null 代表引用类型,new myType() 代表值类型(对应 int、float 等的值为0)。因此,您只需要考虑这两种情况:

object GetDefaultValue(Type t)
{
    if (t.IsValueType)
        return Activator.CreateInstance(t);

    return null;
}

由于值类型始终具有默认构造函数,因此调用Activator.CreateInstance永远不会失败。

24
我发布了相同的解决方案,但是我不确定如何处理可空类型。事实证明,可空类型属于第一分支,但是Activator.CreateInstance(typeof(int?))实际上返回null,所以一切都可以正常工作。 - Josh
12
关于信息(因为我遇到了这个问题),System.Void 被认为是一个 ValueType,但在 CreateInstance 上会引发异常。如果您使用此代码来获取方法的默认返回值,则需要考虑 System.Void! - Vivien Ruiz
1
这将对string失败,而default运算符不会。 - Shimmy Weitzhandler
3
为什么这对字符串会失败?字符串的默认值是null。 - Mike Zboray
4
在这种情况下,Activator.CreateInstance总是返回一个System.Object对象。因此,如果要求它创建一个ValueType类型的实例,则必须将其装箱。Nullable<T>在运行时中有特别处理装箱操作的机制,只返回T而不包含Nullable的包装器。但是,当HasValue为false时,该object引用就是简单的null值。因此,从例如Activator.CreateInstance(typeof(int?))中获取的确实是一个真正的null引用,而不是一个int?。可能会有一个内部的int?,但它在装箱过程中消失了。 - Jonathan Gilbert
显示剩余11条评论

41
您还可以将其作为扩展方法添加到 System.Type 中:
public static class TypeExtensions
{
    public static object GetDefaultValue(this Type t)
    {
        if (t.IsValueType && Nullable.GetUnderlyingType(t) == null)
            return Activator.CreateInstance(t);
        else
            return null;
    }
}

21
那个“else”让我发疯 :) - Mauro Sampietro
1
这不仅仅是关于只有一个“return”,在这种情况下它会使代码变得混乱。实际上,只需删除单词“else”,因为在if中您返回.. if(....)return Activator.CreateInstance(t); return null; - Mauro Sampietro
6
如果想要简洁明了的代码,我会使用三元运算符,这样看起来不会很凌乱。加入else语句会让代码更易于快速理解。而去掉else语句只留下"return null;"会引入一定的隐含性。 - DavidWainwright
1
Nullable.GetUnderlyingType(t) == null 是用来做什么的?在什么情况下它可能不为null? - Muflix
1
对于 Nullable<int>,基础类型将是 int。如果类型是可空的(即基础类型不为 null),我们知道默认值为 null,因此无需创建其实例。 - mtone
显示剩余2条评论

20

在我的系统中已经解决了这个问题,以下是一种在运行时正确确定任意类型默认值的方法,已经针对数千个类型进行了测试:

    /// <summary>
    /// [ <c>public static object GetDefault(this Type type)</c> ]
    /// <para></para>
    /// Retrieves the default value for a given Type
    /// </summary>
    /// <param name="type">The Type for which to get the default value</param>
    /// <returns>The default value for <paramref name="type"/></returns>
    /// <remarks>
    /// If a null Type, a reference Type, or a System.Void Type is supplied, this method always returns null.  If a value type 
    /// is supplied which is not publicly visible or which contains generic parameters, this method will fail with an 
    /// exception.
    /// </remarks>
    /// <example>
    /// To use this method in its native, non-extension form, make a call like:
    /// <code>
    ///     object Default = DefaultValue.GetDefault(someType);
    /// </code>
    /// To use this method in its Type-extension form, make a call like:
    /// <code>
    ///     object Default = someType.GetDefault();
    /// </code>
    /// </example>
    /// <seealso cref="GetDefault&lt;T&gt;"/>
    public static object GetDefault(this Type type)
    {
        // If no Type was supplied, if the Type was a reference type, or if the Type was a System.Void, return null
        if (type == null || !type.IsValueType || type == typeof(void))
            return null;

        // If the supplied Type has generic parameters, its default value cannot be determined
        if (type.ContainsGenericParameters)
            throw new ArgumentException(
                "{" + MethodInfo.GetCurrentMethod() + "} Error:\n\nThe supplied value type <" + type +
                "> contains generic parameters, so the default value cannot be retrieved");

        // If the Type is a primitive type, or if it is another publicly-visible value type (i.e. struct/enum), return a 
        //  default instance of the value type
        if (type.IsPrimitive || !type.IsNotPublic)
        {
            try
            {
                return Activator.CreateInstance(type);
            }
            catch (Exception e)
            {
                throw new ArgumentException(
                    "{" + MethodInfo.GetCurrentMethod() + "} Error:\n\nThe Activator.CreateInstance method could not " +
                    "create a default instance of the supplied value type <" + type +
                    "> (Inner Exception message: \"" + e.Message + "\")", e);
            }
        }

        // Fail with exception
        throw new ArgumentException("{" + MethodInfo.GetCurrentMethod() + "} Error:\n\nThe supplied value type <" + type + 
            "> is not a publicly-visible type, so the default value cannot be retrieved");
    }

在这些例子中,GetDefault方法实现在静态类DefaultValue中。使用以下语句调用此方法:

        object Default = DefaultValue.GetDefault(someType);

要将GetDefault方法作为Type的扩展方法使用,请按如下方式调用:
        object Default = someType.GetDefault();

这种第二种类型扩展方法是一个更简单的客户端代码语法,它消除了调用时引用包含DefaultValue类限定符的需要。

上述运行时形式的GetDefault与C#原始的“default”关键字具有相同的语义,并产生相同的结果。

要使用通用形式的GetDefault,可以访问以下函数:

    /// <summary>
    /// [ <c>public static T GetDefault&lt; T &gt;()</c> ]
    /// <para></para>
    /// Retrieves the default value for a given Type
    /// </summary>
    /// <typeparam name="T">The Type for which to get the default value</typeparam>
    /// <returns>The default value for Type T</returns>
    /// <remarks>
    /// If a reference Type or a System.Void Type is supplied, this method always returns null.  If a value type 
    /// is supplied which is not publicly visible or which contains generic parameters, this method will fail with an 
    /// exception.
    /// </remarks>
    /// <seealso cref="GetDefault(Type)"/>
    public static T GetDefault<T>()
    {
        return (T) GetDefault(typeof(T));
    }

调用通用形式可能是这样的:

一个调用通用形式的示例:

        int? inDefaultVal = DefaultValue.GetDefault<int?>();

当然,对于C#来说,上述GetDefault的通用形式是不必要的,因为它与default(T)相同。 它仅适用于不支持'default'关键字但支持泛型类型的.NET语言。 在大多数情况下,通用形式是不必要的。
一个有用的推论方法是确定对象是否包含其类型的默认值。 我还依赖于以下IsObjectSetToDefault方法来实现此目的:
    /// <summary>
    /// [ <c>public static bool IsObjectSetToDefault(this Type ObjectType, object ObjectValue)</c> ]
    /// <para></para>
    /// Reports whether a value of type T (or a null reference of type T) contains the default value for that Type
    /// </summary>
    /// <remarks>
    /// Reports whether the object is empty or unitialized for a reference type or nullable value type (i.e. is null) or 
    /// whether the object contains a default value for a non-nullable value type (i.e. int = 0, bool = false, etc.)
    /// <para></para>
    /// NOTE: For non-nullable value types, this method introduces a boxing/unboxing performance penalty.
    /// </remarks>
    /// <param name="ObjectType">Type of the object to test</param>
    /// <param name="ObjectValue">The object value to test, or null for a reference Type or nullable value Type</param>
    /// <returns>
    /// true = The object contains the default value for its Type.
    /// <para></para>
    /// false = The object has been changed from its default value.
    /// </returns>
    public static bool IsObjectSetToDefault(this Type ObjectType, object ObjectValue)
    {
        // If no ObjectType was supplied, attempt to determine from ObjectValue
        if (ObjectType == null)
        {
            // If no ObjectValue was supplied, abort
            if (ObjectValue == null)
            {
                MethodBase currmethod = MethodInfo.GetCurrentMethod();
                string ExceptionMsgPrefix = currmethod.DeclaringType + " {" + currmethod + "} Error:\n\n";
                throw new ArgumentNullException(ExceptionMsgPrefix + "Cannot determine the ObjectType from a null Value");
            }

            // Determine ObjectType from ObjectValue
            ObjectType = ObjectValue.GetType();
        }

        // Get the default value of type ObjectType
        object Default = ObjectType.GetDefault();

        // If a non-null ObjectValue was supplied, compare Value with its default value and return the result
        if (ObjectValue != null)
            return ObjectValue.Equals(Default);

        // Since a null ObjectValue was supplied, report whether its default value is null
        return Default == null;
    }

以上的IsObjectSetToDefault方法可以以其本地形式调用,也可以作为Type-class扩展进行访问。

11
如何尝试类似这样的事情呢...
class Program
{
  static void Main(string[] args)
  {
    PrintDefault(typeof(object));
    PrintDefault(typeof(string));
    PrintDefault(typeof(int));
    PrintDefault(typeof(int?));
  }

  private static void PrintDefault(Type type)
  {
    Console.WriteLine("default({0}) = {1}", type,
      DefaultGenerator.GetDefaultValue(type));
  }
}

public class DefaultGenerator
{
  public static object GetDefaultValue(Type parameter)
  {
    var defaultGeneratorType =
      typeof(DefaultGenerator<>).MakeGenericType(parameter);

    return defaultGeneratorType.InvokeMember(
      "GetDefault", 
      BindingFlags.Static |
      BindingFlags.Public |
      BindingFlags.InvokeMethod,
      null, null, new object[0]);
  }
}

public class DefaultGenerator<T>
{
  public static T GetDefault()
  {
    return default(T);
  }
}

它会产生以下输出:
default(System.Object) =
default(System.String) =
default(System.Int32) = 0
default(System.Nullable`1[System.Int32]) =

相当复杂。请参考codeka的解决方案,这是一个更为简洁的方法。 - Josh
我猜这取决于你对“复杂”的定义。如果说24行代码,仅有两个类和三条指令的总和就已经算作“复杂”,那么我想你是正确的... Codeka的示例也只有三条指令,所以我只能假设它是“额外”的类,是吗? - MaxGuernseyIII
除了它不必要的复杂性外,这个答案还引入了一个重大的性能损失,即在使用MakeGenericType和调用外部泛型方法时。请考虑我的解决方案,以解决这些问题:https://dev59.com/6HE95IYBdhLWcg3wCJTj#7881481。 - Mark Jones

2

以下是一个函数,可以返回可空类型的默认值(换句话说,它会为DecimalDecimal?都返回0):

public static object DefaultValue(Type maybeNullable)
{
    Type underlying = Nullable.GetUnderlyingType(maybeNullable);
    if (underlying != null)
        return Activator.CreateInstance(underlying);
    return Activator.CreateInstance(maybeNullable);
}

3
对于可空类型,您不希望返回其 ValueType 的默认值,因为这不是正确的默认值。可空类型的正确默认值是 null。因此,根据您的示例,Decimal 应该具有默认值 0,但是 Decimal? 应该具有默认值 null。请参见我的解决方案,详细说明请查看 https://dev59.com/6HE95IYBdhLWcg3wCJTj#7881481,该解决方案对所有可空类型都有效。 - Mark Jones

2
“默认值”是什么意思?所有引用类型(“class”)的默认值都是null,而所有值类型将根据此表格中的内容具有其默认值。

5
你提到的规则有一个值得注意的例外,即可为空的值类型始终具有默认值为 null,而不是其基础值类型的默认值。可空值类型仍然是值类型。此外,请记住,虽然泛型类定义(type.ContainsGenericParameters == true)在技术上被认为是引用类型,但它没有默认值,因为无法直接实例化。请参考我在 https://dev59.com/6HE95IYBdhLWcg3wCJTj#7881481 上的解决方案获取更多详细信息和示例。 - Mark Jones
1
更像是Nullable<T>类型是一个通用的值类型,其默认值是一个Nullable<T>实例,其Value属性设置为类型T的默认值,其HasValue属性设置为false。我的观点是,可空类型从来不是null,这只是编译器魔法,使得在编写代码时更容易使用可空类型。你所写的:object a = myNullable; 编译器看到的是:object a = myNullable.HasValue ? myNullable.Value : null;。因此,在技术上,Nullables的默认值确实具有其“基础”(通用)类型的默认值 - 但它没有暴露出来。 - AnorZaken

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接