运行时types的默认值
对于任何给定的types,我想知道它的默认值。
在C#中,有一个名为default的关键字
object obj = default(Decimal);
但我有一个types的实例(称为myType),如果我这样说,
object obj = default(myType);
它不工作
有没有什么好办法呢? 我知道一个巨大的开关块可以工作,但这不是一个好的select。
实际上只有两种可能性:对于引用types为null
,对于值types为new myType()
(对于int,float等对应于0)所以你只需要考虑两种情况:
object GetDefaultValue(Type t) { if (t.IsValueType) return Activator.CreateInstance(t); return null; }
(因为值types总是有一个默认的构造函数,所以对Activator.CreateInstance的调用永远不会失败)。
您也可以将其作为扩展方法添加到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; } }
在我自己的系统中解决了这个问题后,下面是一个在运行时正确确定任意types默认值的方法,它已经过数千种types的testing:
/// <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<T>"/> 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 (ie 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();
第二种types扩展方法是更简单的客户端代码语法,因为它消除了在调用中引用包含DefaultValue类限定符的需要。
上面的GetDefault运行时forms与原始C#“default”关键字具有相同的语义,并产生相同的结果。
要使用GetDefault的通用forms,您可以访问以下函数:
/// <summary> /// [ <c>public static T GetDefault< T >()</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?>();
当然,上面的GetDefault通用forms对于C#来说是不必要的,因为它和默认的(T)是一样的。 它只对不支持“默认”关键字但支持genericstypes的.NET语言有用。 在大多数情况下,通用forms是不必要的。
一个有用的推论方法是确定一个对象是否包含其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 (ie is null) or /// whether the object contains a default value for a non-nullable value type (ie 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
方法可以以其本地forms调用,也可以作为Type类扩展来访问。
怎么样像…
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]) =
“默认值”是什么意思? 所有引用types(“类”)都具有null作为默认值,而所有值types将根据此表具有其默认值。
这里有一个函数会返回一个可为空的types的默认值(换句话说,它对于Decimal
和Decimal?
都返回0):
public static object DefaultValue(Type maybeNullable) { Type underlying = Nullable.GetUnderlyingType(maybeNullable); if (underlying != null) return Activator.CreateInstance(underlying); return Activator.CreateInstance(maybeNullable); }