Chương trình tương đương với mặc định (Loại)


514

Tôi đang sử dụng sự phản chiếu để lặp qua Typecác thuộc tính của một và đặt các loại nhất định thành mặc định của chúng. Bây giờ, tôi có thể thực hiện chuyển đổi loại và đặt default(Type)rõ ràng, nhưng tôi muốn thực hiện nó trong một dòng. Có một chương trình tương đương mặc định?


Điều này sẽ hoạt động: Nullable <T> a = new Nullable <T> (). GetValueOrDefault ();
vũ công42

Câu trả lời:


694
  • Trong trường hợp loại giá trị, hãy sử dụng Activator.CreateInstance và nó sẽ hoạt động tốt.
  • Khi sử dụng kiểu tham chiếu chỉ cần trả về null
public static object GetDefault(Type type)
{
   if(type.IsValueType)
   {
      return Activator.CreateInstance(type);
   }
   return null;
}

Trong phiên bản mới hơn của .net, chẳng hạn như .net, type.IsValueTypecần phải được viết làtype.GetTypeInfo().IsValueType


22
Điều này sẽ trả về một loại giá trị được đóng hộp và do đó không tương đương chính xác với loại mặc định (Loại). Tuy nhiên, nó gần như bạn sẽ nhận được mà không có thuốc generic.
Russell Giddings

8
Vậy thì sao? Nếu bạn tìm thấy một loại mà default(T) != (T)(object)default(T) && !(default(T) != default(T))sau đó bạn có một đối số, nếu không nó không quan trọng cho dù nó có được đóng hộp hay không, vì chúng là tương đương.
Miguel Angelo

7
Phần cuối cùng của vị ngữ là để tránh gian lận với quá tải toán tử ... người ta có thể default(T) != default(T)trả về sai, và đó là gian lận! =)
Miguel Angelo

4
Điều này giúp tôi rất nhiều, nhưng tôi nghĩ tôi nên thêm một điều có thể hữu ích cho một số người tìm kiếm câu hỏi này - cũng có một phương pháp tương đương nếu bạn muốn một mảng thuộc loại đã cho và bạn có thể lấy nó bằng cách sử dụng Array.CreateInstance(type, length).
Darrel Hoffman

4
Bạn không lo lắng về việc tạo một thể hiện của một loại giá trị không xác định? Điều này có thể có hiệu ứng tài sản thế chấp.
ygormutti

103

Tại sao không gọi phương thức trả về mặc định (T) bằng phản xạ? Bạn có thể sử dụng GetDefault của bất kỳ loại nào với:

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

    public T GetDefaultGeneric<T>()
    {
        return default(T);
    }

7
Điều này thật tuyệt vời bởi vì nó rất đơn giản. Mặc dù đây không phải là giải pháp tốt nhất ở đây, nhưng đây là một giải pháp quan trọng cần ghi nhớ vì kỹ thuật này có thể hữu ích trong nhiều trường hợp tương tự.
cấu hình

Thay vào đó, nếu bạn gọi phương thức chung là "GetDefault" (nạp chồng), hãy thực hiện việc này: this.GetType (). GetMethod ("GetDefault", Loại mới [0]). <AS_IS>
Stefan Steiger

2
Hãy nhớ rằng, việc thực hiện này chậm hơn nhiều (do sự phản ánh) so với câu trả lời được chấp nhận. Nó vẫn khả thi, nhưng bạn cần thiết lập một số bộ đệm cho các lệnh gọi GetMethod () / MakeGenericMethod () để cải thiện hiệu suất.
Doug

1
Có thể là đối số kiểu là void. Ví dụ: MethodBase.ResultType () của một phương thức void sẽ trả về một đối tượng Type có Tên "Void" hoặc với FullName "System.Void". Do đó, tôi đặt một người bảo vệ: if (t.FullName == "System.Void") trả về null; Cảm ơn giải pháp.
Valo

8
Sử dụng tốt hơn nameof(GetDefaultGeneric)nếu bạn có thể, thay vì"GetDefaultGeneric"
Mugen

87

Bạn có thể sử dụng PropertyInfo.SetValue(obj, null). Nếu được gọi trên một loại giá trị, nó sẽ cung cấp cho bạn mặc định. Hành vi này được ghi lại trong .NET 4.0.NET 4.5 .


7
Đối với câu hỏi cụ thể này - lặp qua máng thuộc tính của một loại VÀ đặt chúng thành "mặc định" - điều này hoạt động rất tốt. Tôi sử dụng nó khi chuyển đổi từ SqlDataReader sang một đối tượng bằng cách sử dụng sự phản chiếu.
Arno Peters

57

Nếu bạn đang sử dụng .NET 4.0 trở lên và bạn muốn có một phiên bản lập trình không phải là mã hóa các quy tắc được xác định bên ngoài mã , bạn có thể tạo Expression, biên dịch và chạy nó một cách nhanh chóng.

Phương thức mở rộng sau đây sẽ lấy a Typevà nhận giá trị được trả về default(T)thông qua Defaultphương thức trên Expressionlớp:

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

Bạn cũng nên lưu trữ giá trị trên dựa trên Type, nhưng lưu ý nếu bạn gọi số này cho một số lượng lớn các Typetrường hợp và không sử dụng nó liên tục, bộ nhớ được sử dụng bởi bộ đệm có thể vượt quá lợi ích.


4
Hiệu suất cho 'return type.IsValueType? Activator.CreateInstance (loại): null; ' nhanh hơn 1000 lần so với e.Compile () ();
Cyrus

1
@Cyrus Tôi khá chắc chắn rằng nó sẽ là cách khác nếu bạn lưu trữ bộ đệm e.Compile(). Đó là toàn bộ điểm của biểu thức.
nawfal

2
Chạy một điểm chuẩn. Rõ ràng, kết quả của e.Compile()nên được lưu vào bộ nhớ cache, nhưng giả sử rằng, phương pháp này nhanh gấp 14 lần ví dụ long. Xem gist.github.com/pvginkel/fed5c8512b9dfefc2870c6853bbfbf8b để biết điểm chuẩn và kết quả.
Pieter van Ginkel

3
Không quan tâm, tại sao bộ nhớ cache e.Compile()hơn e.Compile()()? tức là loại mặc định của một loại có thể thay đổi trong thời gian chạy? Nếu không (như tôi tin là trường hợp này), bạn chỉ có thể lưu trữ kết quả bộ đệm chứ không phải biểu thức được biên dịch, điều này sẽ cải thiện hiệu suất hơn nữa.
JohnLBevan

3
@JohnLBevan - có, và sau đó sẽ không có vấn đề gì khi bạn sử dụng kỹ thuật nào để có kết quả - tất cả sẽ có hiệu suất khấu hao cực nhanh (tra cứu từ điển).
Daniel Earwicker

38

Tại sao bạn nói thuốc generic là ra khỏi hình ảnh?

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

Không thể giải quyết biểu tượng Phương pháp. Sử dụng PCL cho Windows.
Cœur

1
Làm thế nào tốn kém để tạo ra phương thức chung trong thời gian chạy, và sau đó sử dụng nó vài nghìn lần liên tiếp?
C. Tewalt

1
Tôi đã suy nghĩ về một cái gì đó như thế này. Giải pháp tốt nhất và thanh lịch nhất cho tôi. Hoạt động ngay cả trên Compact Framework 2.0. Nếu bạn lo lắng về hiệu suất, bạn luôn có thể lưu trữ phương thức chung, phải không?
Bart

Giải pháp này phù hợp chính xác! Cảm ơn!
Lachezar Lalov

25

Đây là giải pháp tối ưu của 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;
        }
    }
}

2
Một phiên bản ngắn của sự trở lại:return type.IsValueType ? typeDefaults.GetOrAdd(type, Activator.CreateInstance) : null;
Mark Whitfeld

3
Những gì về cấu trúc đột biến? Bạn có biết rằng có thể (và hợp pháp) để sửa đổi các trường của một cấu trúc được đóng hộp, để dữ liệu thay đổi không?
IllidanS4 muốn Monica trở lại vào

@ IllidanS4 là tên của phương thức ngụ ý rằng đây chỉ dành cho các giá trị mặc định của ValueType.
aderesh

8

Câu trả lời được chọn là một câu trả lời tốt, nhưng hãy cẩn thận với đối tượng trả về.

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

Ngoại suy ...

string test = GetDefault(typeof(string));
if (test is string)
     Console.WriteLine("This will never be hit.");

14
đúng, nhưng điều đó cũng đúng với mặc định (chuỗi), như mọi loại tham chiếu khác ...
TDaver

chuỗi là một con chim kỳ lạ - là một loại giá trị cũng có thể trả về null. Nếu bạn muốn mã trả về chuỗi.empty chỉ cần thêm một trường hợp đặc biệt cho nó
Dror Helper

15
@Dror - chuỗi là loại tham chiếu không thay đổi, không phải là loại giá trị.
ljs

@kronoz Bạn nói đúng - Ý tôi là chuỗi đó có thể được xử lý bằng cách trả về chuỗi.empty hoặc null theo nhu cầu.
Người trợ giúp Dror

5

Các biểu thức có thể giúp đỡ ở đây:

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

Tôi đã không kiểm tra đoạn mã này, nhưng tôi nghĩ nó sẽ tạo ra các "null" null cho các loại tham chiếu ..


1
"typed" nulls- giải thích. Đối tượng nào bạn trở về? Nếu bạn trả về một đối tượng kiểu type, nhưng giá trị của nó là null, thì nó không - không thể - có bất kỳ thông tin nào khác ngoài nó null. Bạn không thể truy vấn một nullgiá trị và tìm ra loại được cho là. Nếu bạn không trả về null, nhưng trả lại .. Tôi không biết điều gì .., thì nó sẽ không hoạt động như thế nào null.
ToolmakerSteve

3

Chưa thể tìm thấy bất cứ điều gì đơn giản và thanh lịch, nhưng tôi có một ý tưởng: Nếu bạn biết loại tài sản bạn muốn đặt, bạn có thể tự viết default(T). Có hai trường hợp - Tlà loại giá trị và Tlà loại tham chiếu. Bạn có thể thấy điều này bằng cách kiểm tra T.IsValueType. Nếu Tlà một kiểu tham chiếu, thì bạn có thể chỉ cần đặt nó thành null. Nếu Tlà một loại giá trị, thì nó sẽ có một hàm tạo không tham số mặc định mà bạn có thể gọi để lấy giá trị "trống".


3

Tôi làm nhiệm vụ tương tự như thế này.

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

2

Tương đương với câu trả lời của Dror nhưng là một phương pháp mở rộng:

namespace System
{
    public static class TypeExtensions
    {
        public static object Default(this Type type)
        {
            object output = null;

            if (type.IsValueType)
            {
                output = Activator.CreateInstance(type);
            }

            return output;
        }
    }
}

2

Điều chỉnh nhẹ cho giải pháp của @Rob Fonseca-Ensor : Phương pháp tiện ích mở rộng sau đây cũng hoạt động trên .Net Standard kể từ khi tôi sử dụng GetR nbMethod thay vì GetMethod.

public static class TypeExtensions
{
    public static object GetDefault(this Type t)
    {
        var defaultValue = typeof(TypeExtensions)
            .GetRuntimeMethod(nameof(GetDefaultGeneric), new Type[] { })
            .MakeGenericMethod(t).Invoke(null, null);
        return defaultValue;
    }

    public static T GetDefaultGeneric<T>()
    {
        return default(T);
    }
}

... Và bài kiểm tra theo đơn vị cho những người quan tâm đến chất lượng:

[Fact]
public void GetDefaultTest()
{
    // Arrange
    var type = typeof(DateTime);

    // Act
    var defaultValue = type.GetDefault();

    // Assert
    defaultValue.Should().Be(default(DateTime));
}

0
 /// <summary>
    /// returns the default value of a specified type
    /// </summary>
    /// <param name="type"></param>
    public static object GetDefault(this Type type)
    {
        return type.IsValueType ? (!type.IsGenericType ? Activator.CreateInstance(type) : type.GenericTypeArguments[0].GetDefault() ) : null;
    }

2
Không làm việc cho Nullable<T>loại: nó không trả lại tương đương default(Nullable<T>)mà nên null. Câu trả lời được chấp nhận bởi Dror hoạt động tốt hơn.
Cœur

có thể kiểm tra nếu nullable bằng phản xạ ...
dancer42

0

Điều này sẽ làm việc: Nullable<T> a = new Nullable<T>().GetValueOrDefault();

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.