เทียบเท่าโปรแกรมโดยค่าเริ่มต้น (ประเภท)


514

ฉันใช้การไตร่ตรองเพื่อวนรอบTypeคุณสมบัติของและตั้งค่าบางประเภทเป็นค่าเริ่มต้น ตอนนี้ฉันสามารถสลับกับชนิดและตั้งค่าได้default(Type)อย่างชัดเจน แต่ฉันควรทำในบรรทัดเดียว มีค่าเริ่มต้นเทียบเท่าโปรแกรมหรือไม่


สิ่งนี้ควรใช้งานได้: Nullable <T> a = new Nullable <T> () .GetValueOrDefault ();
dancer42

คำตอบ:


694
  • ในกรณีของประเภทค่าใช้Activator.CreateInstanceและควรจะทำงานได้ดี
  • เมื่อใช้ประเภทการอ้างอิงเพียงแค่คืนค่า null
public static object GetDefault(Type type)
{
   if(type.IsValueType)
   {
      return Activator.CreateInstance(type);
   }
   return null;
}

ใน. net รุ่นใหม่กว่าเช่น. net standard type.IsValueTypeจำเป็นต้องเขียนเป็นtype.GetTypeInfo().IsValueType


22
การดำเนินการนี้จะส่งคืนชนิดของค่าชนิดบรรจุกล่องดังนั้นจึงไม่เท่ากับค่าเริ่มต้นที่แน่นอน (ประเภท) อย่างไรก็ตามมันใกล้เคียงกับที่คุณจะไปโดยไม่มียาชื่อสามัญ
Russell Giddings

8
แล้วอะไรล่ะ หากคุณพบประเภทที่default(T) != (T)(object)default(T) && !(default(T) != default(T))คุณมีข้อโต้แย้งมิฉะนั้นมันไม่สำคัญว่าจะเป็นชนิดบรรจุกล่องหรือไม่เนื่องจากพวกเขาจะเทียบเท่า
Miguel Angelo

7
เพรดิเคตชิ้นสุดท้ายคือการหลีกเลี่ยงการโกงกับโอเปอเรเตอร์ที่บรรทุกเกินพิกัด ... ใคร ๆ ก็สามารถdefault(T) != default(T)คืนค่าผิด ๆ ได้และนั่นก็คือการโกง! =)
Miguel Angelo

4
นี้ช่วยให้ฉันมาก แต่ผมคิดว่าผมควรจะเพิ่มสิ่งหนึ่งที่อาจเป็นประโยชน์กับบางคนที่ค้นหาคำถามนี้ - นอกจากนี้ยังมีวิธีการเทียบเท่าถ้าคุณอยากอาร์เรย์Array.CreateInstance(type, length)ชนิดที่กำหนดและคุณจะได้รับโดยใช้
Darrel Hoffman

4
คุณไม่ต้องกังวลเกี่ยวกับการสร้างอินสแตนซ์ของประเภทค่าที่ไม่รู้จักใช่ไหม สิ่งนี้อาจมีผลกระทบที่เป็นหลักประกัน
ygormutti

103

ทำไมไม่เรียกวิธีการที่ส่งกลับค่าเริ่มต้น (T) ด้วยการสะท้อน? คุณสามารถใช้ GetDefault ประเภทใดก็ได้ด้วย:

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

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

7
มันยอดเยี่ยมเพราะมันง่ายมาก แม้ว่านี่ไม่ใช่วิธีแก้ปัญหาที่ดีที่สุด แต่เป็นวิธีแก้ปัญหาสำคัญที่ควรคำนึงถึงเพราะเทคนิคนี้มีประโยชน์ในหลาย ๆ สถานการณ์ที่คล้ายกัน
กำหนดค่า

หากคุณเรียกใช้เมธอดทั่วไป "GetDefault" แทน (โหลดมากเกินไป) ให้ทำดังนี้: this.GetType (). GetMethod ("GetDefault", ประเภทใหม่ [0]) <AS_IS>
Stefan Steiger

2
โปรดทราบว่าการดำเนินการนี้ช้ากว่าคำตอบที่ยอมรับ มันยังทำงานได้ แต่คุณต้องตั้งค่าแคชสำหรับการโทร GetMethod () / MakeGenericMethod () เพื่อปรับปรุงประสิทธิภาพ
Doug

1
เป็นไปได้ว่าอาร์กิวเมนต์ประเภทนั้นเป็นโมฆะ เช่น MethodBase.ResultType () ของเมธอด void จะส่งคืนวัตถุชนิดที่มีชื่อ "Void" หรือด้วย FullName "System.Void" ดังนั้นฉันใส่ยาม: ถ้า (t.FullName == "System.Void") กลับ null; ขอบคุณสำหรับการแก้ปัญหา
Valo

8
ควรใช้ให้ดีกว่าnameof(GetDefaultGeneric)แทน"GetDefaultGeneric"
Mugen

87

PropertyInfo.SetValue(obj, null)คุณสามารถใช้ หากเรียกใช้กับประเภทค่ามันจะให้ค่าเริ่มต้นแก่คุณ ลักษณะการทำงานนี้เป็นเอกสารใน .NET 4.0และใน .NET 4.5


7
สำหรับคำถามเฉพาะนี้ - การวนลูปรางคุณสมบัติของประเภทและตั้งให้เป็น "ค่าเริ่มต้น" - มันทำงานได้อย่างยอดเยี่ยม ฉันใช้มันเมื่อแปลงจาก SqlDataReader เป็นวัตถุโดยใช้การสะท้อน
Arno Peters

57

หากคุณใช้. 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()();
}

คุณควรแคชค่าข้างต้นตามTypeด้วย แต่โปรดระวังหากคุณเรียกใช้Typeอินสแตนซ์นี้เป็นจำนวนมากและไม่ใช้อย่างต่อเนื่องหน่วยความจำที่แคชใช้อาจมีประโยชน์มากกว่า


4
ประสิทธิภาพสำหรับ 'return type.IsValueType? Activator.CreateInstance (ประเภท): null; ' เร็วกว่า 1,000 เท่า e.Compile () ();
Cyrus

1
@Cyrus ผมค่อนข้างแน่ใจว่ามันจะเป็นรอบวิธีอื่น ๆ e.Compile()ถ้าคุณแคช นั่นคือจุดรวมของการแสดงออก
nawfal

2
วิ่งเกณฑ์มาตรฐาน เห็นได้ชัดว่าผลจากการe.Compile()ควรจะเก็บไว้ แต่สมมติว่าวิธีการนี้คือประมาณ 14 longเท่าเป็นอย่างรวดเร็วสำหรับเช่น ดูgist.github.com/pvginkel/fed5c8512b9dfefc2870c6853bbfbf8bเพื่อดูเกณฑ์มาตรฐานและผลลัพธ์
Pieter van Ginkel

3
ออกจากดอกเบี้ยทำไมแคชe.Compile()มากกว่าe.Compile()()? เช่นประเภทเริ่มต้นของประเภทสามารถเปลี่ยนที่รันไทม์? หากไม่ใช่ (เพราะฉันเชื่อว่าเป็นกรณีนี้) คุณสามารถเก็บแคชผลลัพธ์แทนนิพจน์ที่คอมไพล์ซึ่งควรปรับปรุงประสิทธิภาพต่อไป
JohnLBevan

3
@JohnLBevan - ใช่แล้วมันจะไม่สำคัญว่าคุณใช้เทคนิคเพื่อให้ได้ผลลัพธ์ - ทั้งหมดจะมีประสิทธิภาพการตัดจำหน่ายอย่างรวดเร็วมาก (การค้นหาพจนานุกรม)
Daniel Earwicker

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

ไม่สามารถแก้ไขวิธีสัญลักษณ์ ใช้ PCL สำหรับ Windows
Cœur

1
ราคาแพงแค่ไหนในการสร้างวิธีการทั่วไปในขณะใช้งานและจากนั้นใช้หลายพันครั้งติดต่อกัน
C. Tewalt

1
ฉันกำลังคิดเกี่ยวกับสิ่งนี้ ทางออกที่ดีที่สุดและหรูหราที่สุดสำหรับฉัน ทำงานได้แม้ใน Compact Framework 2.0 หากคุณกังวลเกี่ยวกับประสิทธิภาพคุณสามารถแคชวิธีทั่วไปได้ใช่ไหม
บาร์ต

โซลูชันนี้เหมาะสมอย่างแน่นอน! ขอบคุณ!
Lachezar Lalov

25

นี่คือโซลูชันของ 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
ฉบับย่อของการกลับมา:return type.IsValueType ? typeDefaults.GetOrAdd(type, Activator.CreateInstance) : null;
Mark Whitfeld

3
อะไรคือโครงสร้างที่ไม่แน่นอน คุณรู้หรือไม่ว่าเป็นไปได้ (และถูกกฎหมาย) ในการปรับเปลี่ยนเขตข้อมูลของโครงสร้างแบบกล่องเพื่อให้ข้อมูลเปลี่ยนแปลง
IllidanS4 ต้องการโมนิก้ากลับเมื่อ

@ IllidanS4 เป็นชื่อของวิธีการแสดงถึงสิ่งนี้มีไว้สำหรับค่าของ ValueType เริ่มต้นเท่านั้น
aderesh

8

คำตอบที่เลือกนั้นเป็นคำตอบที่ดี แต่ระวังด้วยการส่งคืนวัตถุ

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

คะเน ...

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

14
จริง แต่ที่ถือเป็นค่าเริ่มต้น (สตริง) รวมเป็นชนิดการอ้างอิงอื่น ๆ ทุก ...
TDaver

string เป็นนกแปลก ๆ - เป็นประเภทค่าที่สามารถคืนค่าว่างได้เช่นกัน ถ้าคุณต้องการให้โค้ดส่งคืนสตริงว่างเพียงแค่เพิ่มเคสพิเศษสำหรับมัน
Dror Helper

15
@Dror - สตริงเป็นประเภทการอ้างอิงที่ไม่เปลี่ยนรูปไม่ได้เป็นประเภทค่า
ljs

@ kronoz คุณพูดถูก - ฉันหมายถึงสตริงนั้นสามารถจัดการได้โดยส่งคืนสตริงว่างหรือเป็นโมฆะตามต้องการ
Dror Helper

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

ฉันไม่ได้ทดสอบตัวอย่างนี้ แต่ฉันคิดว่าควรสร้าง "null" ที่พิมพ์สำหรับประเภทการอ้างอิง ..


1
"typed" nulls- อธิบาย คุณจะกลับวัตถุอะไร ถ้าคุณกลับวัตถุของการพิมพ์typeแต่ค่าของมันเป็นnullแล้วมันไม่ได้ - ไม่สามารถ - nullมีข้อมูลอื่นใดนอกเหนือจากที่มันเป็น คุณไม่สามารถสืบค้นnullค่าและค้นหาประเภทที่คาดคะเน หากคุณไม่กลับมาเป็นโมฆะ แต่กลับมา .. ฉันไม่รู้ว่า .. แล้วมันจะไม่เป็นเช่นnullนั้น
ToolmakerSteve

3

ยังหาสิ่งที่เรียบง่ายและสง่างามไม่ได้ แต่ฉันมีความคิดอย่างหนึ่ง: หากคุณทราบประเภทของอสังหาริมทรัพย์ที่คุณต้องการตั้งค่าคุณสามารถเขียนเองdefault(T)ได้ มีสองกรณี - Tเป็นประเภทค่าและTเป็นประเภทอ้างอิง T.IsValueTypeคุณสามารถดูนี้โดยการตรวจสอบ ถ้าเป็นชนิดอ้างอิงแล้วคุณก็สามารถตั้งค่าให้T nullหากTเป็นประเภทค่าจะมีตัวสร้างแบบไร้พารามิเตอร์ที่คุณสามารถเรียกเพื่อรับค่า "blank"


3

ฉันทำสิ่งเดียวกันนี้

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

เทียบเท่ากับคำตอบของ Dror แต่เป็นวิธีการขยาย:

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

การปรับเปลี่ยนเล็กน้อยสำหรับโซลูชันของ @Rob Fonseca-Ensor : วิธีการขยายต่อไปนี้ทำงานบน. Net Standard เนื่องจากฉันใช้ GetRuntimeMethod แทน 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);
    }
}

... และการทดสอบตามหน่วยสำหรับผู้ที่สนใจเกี่ยวกับคุณภาพ:

[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
ไม่ทำงานสำหรับNullable<T>ประเภท: มันไม่ได้กลับมาเทียบเท่าของที่ควรจะเป็นdefault(Nullable<T>) nullคำตอบที่ได้รับการยอมรับจาก Dror ทำงานได้ดีขึ้น
Cœur

สามารถตรวจสอบว่าเป็น
โมฆะ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.