程序化等效的默认值(类型)

tag*_*s2k 492 c# reflection default

我正在使用反射来遍历一个Type属性并将某些类型设置为默认值.现在,我可以对类型进行切换并default(Type)明确设置,但我宁愿在一行中进行.有默认的程序化等价物吗?

Dro*_*per 667

public static object GetDefault(Type type)
{
   if(type.IsValueType)
   {
      return Activator.CreateInstance(type);
   }
   return null;
}
Run Code Online (Sandbox Code Playgroud)

在较新版本的.net中,例如.net标准,type.IsValueType需要写成type.GetTypeInfo().IsValueType

  • 这将返回一个盒装值类型,因此不是默认值(Type).然而,它与你没有泛型的情况一样接近. (21认同)
  • 所以呢?如果你找到一个类型`default(T)!=(T)(object)default(T)&&!(default(T)!= default(T))`那么你有一个参数,否则无关紧要它是盒装还是不装盒,因为它们是等价的. (7认同)
  • 谓词的最后一部分是避免操作符重载的欺骗行为......可以使`default(T)!= default(T)`返回false,这就是作弊!=) (7认同)
  • 这对我有很大帮助,但我认为我应该添加一些可能对搜索这个问题的人有用的东西 - 如果你想要一个给定类型的_array_,你也可以通过使用`Array来获得它. CreateInstance(类型,长度)`. (4认同)
  • 您不担心创建未知值类型的实例吗?这可能会产生附带影响. (4认同)
  • 如果允许在结构上定义自定义无参数构造函数(可能在C#6中),则这将不再是正确的解决方案. (2认同)
  • 这对`System.Net.IPAddress`不起作用,因为该类不提供无参数构造函数.`default(IPAddress)`但确实有效,是`IPAddress.None`.不确定如何使用运行时类型执行此操作. (2认同)
  • 如果您正在对基于 Enum 的类型的此方法的结果进行相等比较,请注意您不能使用常规 == 相等运算符。有关更多信息,请参阅 [这个问题](http://stackoverflow.com/questions/652673/enum-boxing-and-equality)。 (2认同)
  • 只是一个注释(在这7(!)年回答中):使用`Activator.CreateInstance`比使用带有`GetConstructor`的反射来存储对任何值类型的构造函数的引用(重用)要慢一些.它是否隐藏(与@Jannes示例一样)并不重要.使用泛型它可以做更多的工作,但仍然要快得多.当然,使用反射的人往往不期望速度,但仍然如此. (2认同)
  • @Jannes为什么不呢?IPAddress是引用类型,因此应该只返回null? (2认同)

dra*_*707 99

为什么不用反射调用返回默认值(T)的方法?您可以使用任何类型的GetDefault:

    public object GetDefault(Type t)
    {
        return this.GetType().GetMethod("GetDefaultGeneric").MakeGenericMethod(t).Invoke(this, null);
    }

    public T GetDefaultGeneric<T>()
    {
        return default(T);
    }
Run Code Online (Sandbox Code Playgroud)

  • 这很棒,因为它很简单.虽然这不是最好的解决方案,但这是一个重要的解决方案,因为这种技术在很多类似的情况下都很有用. (7认同)
  • 如果可以的话,最好使用`nameof(GetDefaultGeneric)`,而不是``GetDefaultGeneric"` (6认同)
  • 请记住,此实现比接受的答案慢得多(由于反射).它仍然可行,但您需要为GetMethod()/ MakeGenericMethod()调用设置一些缓存以提高性能. (2认同)
  • 类型参数可能为空。例如,void 方法的 MethodBase.ResultType() 将返回名称为“Void”或全名为“System.Void”的 Type 对象。因此我设置了一个保护: if (t.FullName=="System.Void") return null; 感谢您的解决方案。 (2认同)

Joe*_*Fan 82

你可以用PropertyInfo.SetValue(obj, null).如果调用值类型,它将为您提供默认值..NET 4.0.NET 4.5中记录此行为.

  • 对于这个特定的问题 - 通过类型的属性循环并将它们设置为"默认" - 这非常有效.我在使用反射从SqlDataReader转换为对象时使用它. (6认同)
  • 这只适用于您想要填写的房产吗? (4认同)

cas*_*One 54

如果您使用的是.NET 4.0或更高版本,并且您希望编程版本不是代码之外定义的规则的编码,您可以在Expression运行中创建,编译和运行它.

下面的扩展方法将采取Type,并从返回的值default(T)通过Default方法Expression类:

public static T GetDefaultValue<T>()
{
    // We want an Func<T> which returns the default.
    // Create that expression here.
    Expression<Func<T>> e = Expression.Lambda<Func<T>>(
        // The default value, always get what the *code* tells us.
        Expression.Default(typeof(T))
    );

    // Compile and return the value.
    return e.Compile()();
}

public static object GetDefaultValue(this Type type)
{
    // Validate parameters.
    if (type == null) throw new ArgumentNullException("type");

    // We want an Func<object> which returns the default.
    // Create that expression here.
    Expression<Func<object>> e = Expression.Lambda<Func<object>>(
        // Have to convert to object.
        Expression.Convert(
            // The default value, always get what the *code* tells us.
            Expression.Default(type), typeof(object)
        )
    );

    // Compile and return the value.
    return e.Compile()();
}
Run Code Online (Sandbox Code Playgroud)

您还应该根据它缓存上述值Type,但要注意,如果您为大量Type实例调用此值,并且不要经常使用它,则缓存所消耗的内存可能会超过收益.

  • 'return type.IsValueType?的性能?Activator.CreateInstance(type):null;' 比e.Compile()()快1000倍; (4认同)
  • @Cyrus我相当确定如果你缓存`e.Compile()`,情况会是相反的。这就是表达式的全部要点。 (2认同)
  • 跑一个基准.显然,应该缓存`e.Compile()`的结果,但假设这个方法的速度大约是'long`的14倍.有关基准和结果,请参阅https://gist.github.com/pvginkel/fed5c8512b9dfefc2870c6853bbfbf8b. (2认同)
  • 出于兴趣,为什么要缓存`e.Compile()`而不是`e.Compile()()`?即类型的默认类型是否可以在运行时更改?如果不是(我认为是这种情况)你可以只存储缓存结果而不是编译表达式,这将进一步提高性能. (2认同)
  • @JohnLBevan - 是的,然后使用什么技术来获得结果并不重要 - 所有这些都将具有极快的摊销性能(字典查找). (2认同)

Rob*_*sor 38

为什么你说仿制药不合适?

    public static object GetDefault(Type t)
    {
        Func<object> f = GetDefault<object>;
        return f.Method.GetGenericMethodDefinition().MakeGenericMethod(t).Invoke(null, null);
    }

    private static T GetDefault<T>()
    {
        return default(T);
    }
Run Code Online (Sandbox Code Playgroud)


小智 24

这是优化Flem的解决方案:

using System.Collections.Concurrent;

namespace System
{
    public static class TypeExtension
    {
        //a thread-safe way to hold default instances created at run-time
        private static ConcurrentDictionary<Type, object> typeDefaults =
           new ConcurrentDictionary<Type, object>();

        public static object GetDefaultValue(this Type type)
        {
            return type.IsValueType
               ? typeDefaults.GetOrAdd(type, Activator.CreateInstance)
               : null;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 可变结构怎么样?您是否知道修改盒装结构的字段是可能的(也是合法的),以便数据发生变化? (3认同)
  • 返回的简写版本:`return type.IsValueType?typeDefaults.GetOrAdd(type,Activator.CreateInstance):null;` (2认同)

Tin*_*ter 10

这里有几个答案使用Activator.CreateInstance. 然而,自从做出了这些答案后,C# 和 .NET 进行了更改,允许用户使用无参数构造函数定义结构(文档链接)。 Activator.CreateInstance如果定义了,将执行这样的无参数构造函数,并且您将不再获得默认实例。

相反,您可以使用RuntimeHelpers.GetUninitializedObject,如下所示:

//using System.Runtime.CompilerServices
public static object GetDefault(Type type)
{
    // it's very important to check IsValueType before calling GetUninitializedObject
    // GetUninitializedObject is valid for reference types, but it will not return null
    if (type.IsValueType)
    {
        return RuntimeHelpers.GetUninitializedObject(type);
    }
    return null;
}
Run Code Online (Sandbox Code Playgroud)

您可以将其作为单行执行的另一个选项是使用Array.CreateInstance

// works for both reference types and value types
static object GetDefault(Type type) => Array.CreateInstance(type, 1).GetValue(0);
Run Code Online (Sandbox Code Playgroud)

编辑:之前的答案建议使用,FormatterServices.GetUninitializedObject但从 .NET 8 开始已弃用。

  • 我同意这应该是首选方式,因为这也是[微软代码执行此操作的方式](https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR .cs,cccbda6331263481,参考文献)。除了他们使用 [`System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject`](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.runtimehelpers.getuninitializedobject),它也称为通过 FormatterServices.GetUninitializedObject。 (3认同)

BSi*_*ck7 7

选择的答案是一个很好的答案,但要小心返回的对象.

string test = null;
string test2 = "";
if (test is string)
     Console.WriteLine("This will never be hit.");
if (test2 is string)
     Console.WriteLine("Always hit.");
Run Code Online (Sandbox Code Playgroud)

推断...

string test = GetDefault(typeof(string));
if (test is string)
     Console.WriteLine("This will never be hit.");
Run Code Online (Sandbox Code Playgroud)

  • @Dror - string是一个不可变的引用类型,而不是值类型. (15认同)
  • 是的,但这也适用于默认(字符串),与其他所有引用类型一样...... (14认同)

kpo*_*ock 5

我像这样做同样的任务。

//in MessageHeader 
   private void SetValuesDefault()
   {
        MessageHeader header = this;             
        Framework.ObjectPropertyHelper.SetPropertiesToDefault<MessageHeader>(this);
   }

//in ObjectPropertyHelper
   public static void SetPropertiesToDefault<T>(T obj) 
   {
            Type objectType = typeof(T);

            System.Reflection.PropertyInfo [] props = objectType.GetProperties();

            foreach (System.Reflection.PropertyInfo property in props)
            {
                if (property.CanWrite)
                {
                    string propertyName = property.Name;
                    Type propertyType = property.PropertyType;

                    object value = TypeHelper.DefaultForType(propertyType);
                    property.SetValue(obj, value, null);
                }
            }
    }

//in TypeHelper
    public static object DefaultForType(Type targetType)
    {
        return targetType.IsValueType ? Activator.CreateInstance(targetType) : null;
    }
Run Code Online (Sandbox Code Playgroud)


Kon*_*aev 5

表达式可以在这里提供帮助:

    private static Dictionary<Type, Delegate> lambdasMap = new Dictionary<Type, Delegate>();

    private object GetTypedNull(Type type)
    {
        Delegate func;
        if (!lambdasMap.TryGetValue(type, out func))
        {
            var body = Expression.Default(type);
            var lambda = Expression.Lambda(body);
            func = lambda.Compile();
            lambdasMap[type] = func;
        }
        return func.DynamicInvoke();
    }
Run Code Online (Sandbox Code Playgroud)

我没有测试这个片段,但我认为它应该为引用类型生成"类型"空值.