运行时类型的默认值

vik*_*iky 148 c# types default

对于任何给定的类型,我想知道它的默认值.

在C#中,有一个名为default的关键字,就像这样做

object obj = default(Decimal);
Run Code Online (Sandbox Code Playgroud)

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

object obj = default(myType);
Run Code Online (Sandbox Code Playgroud)

它不起作用

这样做有什么好办法吗?我知道一个巨大的开关块可以工作,但这不是一个好的选择.

Dea*_*ing 249

实际上只有两种可能:null引用类型和new myType()值类型(对应于int,float等对应的0)所以你真的只需要考虑两种情况:

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

    return null;
}
Run Code Online (Sandbox Code Playgroud)

(因为值类型总是有一个默认构造函数,所以对Activator.CreateInstance的调用永远不会失败).

  • 我发布了相同的解决方案,但我不确定如何处理nullables.事实证明,nullables属于第一个分支,但Activator.CreateInstance(typeof(int?))实际上返回null,所以一切都解决了. (21认同)
  • 有关信息(因为我遇到了问题),System.Void被认为是ValueType,但在CreateInstance上导致异常.如果您使用此代码获取方法的默认返回值,则需要考虑System.Void! (10认同)
  • @Shimmy为什么这会失败?string的默认值为null. (3认同)
  • @Gabor实际上在这种情况下发生的事情是`Activator.CreateInstance`总是返回一个`System.Object`,所以如果你要求它创建一个`ValueType`的实例,它必须将它打包.`Nullable <T>`在运行时用于装箱的特殊外壳,以便`Nullable`包装器消失,只返回`T`.但是,当`HasValue`为假时,`object`引用只是`null`.因此,它实际上*是*合法地来自例如`Activator.CreateInstance(typeof(int?))`的实际`null`引用,而不是`int?`.内部可能有一个`int?`但它在拳击期间消失了. (3认同)

Dav*_*ght 33

您还可以将其作为扩展方法添加到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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 那'别的'让我疯狂:) (13认同)
  • 如果我想要简洁,我会使用三元运算符 - 它看起来并不凌乱.包括else意味着代码更容易掌握.删除它,只使用"return null;" 引入隐含性. (3认同)

Mar*_*nes 18

在我自己的系统中解决了这个问题之后,这里有一个正确确定运行时任意Type的默认值的方法,该方法已针对数千种类型进行了测试:

    /// <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");
    }
Run Code Online (Sandbox Code Playgroud)

在这些示例中,GetDefault方法在静态类DefaultValue中实现.使用如下语句调用此方法:

        object Default = DefaultValue.GetDefault(someType);
Run Code Online (Sandbox Code Playgroud)

要将GetDefault方法用作Type的扩展方法,请将其命名为:

        object Default = someType.GetDefault();
Run Code Online (Sandbox Code Playgroud)

第二种类型扩展方法是一种更简单的客户端代码语法,因为它不需要在调用时引用包含的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));
    }
Run Code Online (Sandbox Code Playgroud)

对通用表单的调用可能类似于:

        int? inDefaultVal = DefaultValue.GetDefault<int?>();
Run Code Online (Sandbox Code Playgroud)

当然,上述通用形式的GetDefault对于C#来说是不必要的,因为它的工作原理与默认(T)相同.它仅对不支持'default'关键字但支持泛型类型的.NET语言有用.在大多数情况下,通用表单是不必要的.

一个有用的推论方法是确定对象是否包含其Type的默认值.我还为此目的依赖以下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;
    }
Run Code Online (Sandbox Code Playgroud)

上述IsObjectSetToDefault方法既可以以其本机形式调用,也可以作为Type-class扩展进行访问.


小智 10

怎么样......

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

它产生以下输出:

default(System.Object) =
default(System.String) =
default(System.Int32) = 0
default(System.Nullable`1[System.Int32]) =
Run Code Online (Sandbox Code Playgroud)