2010-03-22 30 views
131

Đối với bất kỳ loại đã cho nào tôi muốn biết giá trị mặc định của nó.Giá trị mặc định của một loại tại thời gian chạy

Trong C#, có một từ khóa được gọi là mặc định để làm điều này như

object obj = default(Decimal); 

nhưng tôi có một thể hiện của loại (gọi là myType) và nếu tôi nói điều này,

object obj = default(myType); 

nó doesn 't work

Có cách nào tốt để thực hiện việc này không? Tôi biết rằng một khối chuyển đổi lớn sẽ hoạt động nhưng đó không phải là một lựa chọn tốt.

+0

Bạn có thể giải thích lý do tại sao nó không hoạt động? Bạn gặp lỗi? Liệu nó chỉ không trả lại những gì bạn mong đợi? – Gabe

+0

@Josh, Cảm ơn! bạn thích nó – viky

+1

@gabe, Nó hoạt động với tên loại nhưng không phải với loại Ví dụ của tên kiểu đó, tôi có nghĩa là mặc định (Thập phân) hoạt động nhưng mặc định (typeof (Thập phân)) không – viky

Trả lời

214

Có thực sự chỉ có hai khả năng: null với nhiều loại tài liệu tham khảo và new myType() với nhiều loại giá trị (tương ứng với 0 cho int, float, vv) Vì vậy, bạn thực sự chỉ cần tính đến hai trường hợp:

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

    return null; 
} 

(Vì các kiểu giá trị luôn có một hàm tạo mặc định, nên lệnh gọi tới Activator.CreateInstance sẽ không bao giờ thất bại).

+12

Tôi đã đăng giải pháp tương tự nhưng tôi không chắc chắn về cách xử lý null. Nó chỉ ra nullables rơi vào chi nhánh đầu tiên nhưng Activator.CreateInstance (typeof (int?)) Thực sự trả về null vì vậy nó tất cả các công trình ra. – Josh

+0

@Josh: Thú vị ... Tôi đã kiểm tra nhanh giải pháp của mình và 'int? 'Xuất hiện với' null' được mong đợi, nhưng tôi đã không thực sự kiểm tra xem 'typeof (int?). IsValueType' có trả về true hay không sai. –

+0

Vâng. Điều này đưa bạn vào hoạt động kinh doanh lông của việc thực hiện lại toán tử mặc định của trình biên dịch C#() trong thời gian chạy. Nó khá đơn giản để làm nhưng, nếu các quy tắc để mặc định đã được mở rộng để đưa vào tài khoản một kịch bản mới, bạn phải cập nhật mã của bạn. –

9

Làm thế nào về một cái gì đó giống như ...

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

Nó tạo ra kết quả như sau:

default(System.Object) = 
default(System.String) = 
default(System.Int32) = 0 
default(System.Nullable`1[System.Int32]) = 
+0

Khá phức tạp. Xem giải pháp của codeka cho một phương pháp ngắn gọn hơn nhiều. – Josh

+0

+1 thats một ví dụ tốt đẹp nhưng thực sự tôi không cần nhiều. – viky

+0

Tôi đoán điều đó phụ thuộc vào định nghĩa phức tạp của bạn. Nếu hai mươi bốn dòng mã với tổng cộng hai lớp và ba hướng dẫn là "phức tạp", thì tôi đoán bạn đúng ... Ví dụ của Codeka cũng có ba hướng dẫn để tôi chỉ có thể giả định rằng đó là "phụ" lớp học, sau đó? –

2

Bạn có ý nghĩa gì với "Giá trị mặc định"? Tất cả các loại tham chiếu ("lớp") có giá trị mặc định là null, trong khi tất cả các loại giá trị sẽ có giá trị mặc định của chúng theo this table.

+5

Có một ngoại lệ đáng chú ý đối với các quy tắc bạn đã đề cập. Nghĩa là, một ValueType nullable luôn có một giá trị mặc định là null, không phải là một giá trị mặc định của ValueType nằm bên dưới của nó. Một ValueType nullable vẫn là một ValueType. Cũng nên nhớ rằng mặc dù một định nghĩa lớp chung chung (type.ContainsGenericParameters == true) về mặt kỹ thuật được coi là một kiểu tham chiếu, nó không có giá trị mặc định, vì nó không thể được khởi tạo trực tiếp. Vui lòng tham khảo giải pháp của tôi tại http://stackoverflow.com/questions/2490244/default-value-of-a-type/7881481#7881481 để biết thêm chi tiết và ví dụ. –

+1

... Giống như kiểu Nullable là một ValueType chung và giá trị mặc định của nó là một thể hiện của Nullable với thuộc tính Giá trị được đặt thành giá trị mặc định của loại T và thuộc tính HasValue được đặt thành false. Quan điểm của tôi là một kiểu vô hiệu hóa _is_ không bao giờ rỗng, đó chỉ là phép thuật biên dịch để làm cho nullables dễ sử dụng hơn khi bạn đang viết mã. Những gì bạn viết: 'object a = myNullable;' những gì trình biên dịch thấy: 'object a = myNullable.HasValue? myNullable.Value: null; '. Vì vậy, về mặt kỹ thuật, mặc định của Nullables _does_ có giá trị mặc định của loại "cơ bản" (chung) - nhưng nó không bị lộ. – AnorZaken

1

Dưới đây là một chức năng mà sẽ trả về giá trị mặc định cho một loại nullable (nói cách khác, nó sẽ trả về 0 cho cả DecimalDecimal?):

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

Bạn không muốn trả về giá trị mặc định ValueType cho một loại có thể vô hiệu hóa, vì đây không phải là mặc định chính xác. Mặc định chính xác cho một nullable Type là null. Vì vậy, liên quan đến ví dụ của bạn, thập phân nên có một mặc định là 0, nhưng thập phân? nên có giá trị mặc định là null. Xem giải pháp của tôi chi tiết tại http://stackoverflow.com/questions/2490244/default-value-of-a-type/7881481#7881481, hoạt động chính xác cho tất cả các Loại có thể sử dụng được. –

24

Bạn cũng có thể thêm nó như là một phương pháp mở rộng hệ thống .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; 
    } 
} 
+6

Điều đó 'khác' khiến tôi phát điên: – sam

+0

trả lại (t.IsValueType && Nullable.GetUnderlyingType (t) == null)? Activator.CreateInstance (t): null; nếu bạn không thích nó treo ở đó! – coalvilledave

+0

Đó không phải là về việc chỉ có một 'trở lại' nó sẽ làm cho mã lộn xộn trong trường hợp này. Thực sự chỉ cần loại bỏ từ 'else' kể từ khi bạn quay trở lại .. if (....) trả về Activator.CreateInstance (t); trả về null; – sam

15

Sau khi giải quyết vấn đề này trong các hệ thống của riêng tôi, đây là một phương pháp để xác định một cách chính xác giá trị mặc định của một loại tùy vào thời gian chạy, mà đã được thử nghiệm chống lại hàng ngàn loại:

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

Trong các ví dụ này, phương pháp GetDefault được triển khai trong lớp tĩnh DefaultValue. Gọi phương pháp này với một tuyên bố như:

 object Default = DefaultValue.GetDefault(someType); 

Để sử dụng phương pháp GetDefault như một phương pháp mở rộng cho Type, gọi nó là như thế này:

 object Default = someType.GetDefault(); 

thứ hai này, cách tiếp cận Type-phần mở rộng là một khách hàng đơn giản cú pháp-mã, vì nó loại bỏ sự cần thiết phải tham chiếu vòng loại lớp có chứa DefaultValue trên cuộc gọi.

Biểu mẫu thời gian chạy trên của GetDefault hoạt động với ngữ nghĩa giống hệt như từ khóa C# 'mặc định' nguyên thủy và tạo ra kết quả tương tự.

Để sử dụng một hình thức chung của GetDefault, bạn có thể truy cập vào các chức năng sau:

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

Một cuộc gọi đến các hình thức chung chung có thể là một cái gì đó như:

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

Tất nhiên, generic trên hình thức GetDefault là không cần thiết cho C#, vì nó hoạt động giống như mặc định (T). Nó chỉ hữu ích cho một ngôn ngữ .NET không hỗ trợ từ khóa 'mặc định' nhưng nó hỗ trợ các kiểu generic. Trong hầu hết các trường hợp, hình thức chung là không cần thiết.

Phương pháp hệ quả hữu ích là phương pháp xác định xem đối tượng có chứa giá trị mặc định cho Loại của nó hay không. Tôi cũng dựa vào phương pháp IsObjectSetToDefault sau cho mục đích đó:

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

Các IsObjectSetToDefault phương pháp trên hoặc có thể được gọi là ở dạng tự nhiên của nó hoặc truy cập như một phần mở rộng Type-class.

Các vấn đề liên quan