Setara programatik dari standar (Tipe)

514

Saya menggunakan refleksi untuk mengulangi Typeproperti a dan mengatur tipe tertentu ke default. Sekarang, saya bisa melakukan saklar pada jenis dan mengatur default(Type)secara eksplisit, tetapi saya lebih suka melakukannya dalam satu baris. Apakah ada program yang setara dengan standar?

tags2k
sumber
Ini seharusnya berfungsi: Nullable <T> a = new Nullable <T> () .GetValueOrDefault ();
dancer42

Jawaban:

694
  • Dalam hal tipe nilai gunakan Activator.CreateInstance dan itu akan berfungsi dengan baik.
  • Saat menggunakan tipe referensi, kembalikan null saja
public static object GetDefault(Type type)
{
   if(type.IsValueType)
   {
      return Activator.CreateInstance(type);
   }
   return null;
}

Dalam versi .net yang lebih baru seperti standar .net, type.IsValueTypeperlu ditulis sebagai .nettype.GetTypeInfo().IsValueType

Dror Helper
sumber
22
Ini akan mengembalikan tipe nilai kotak dan oleh karena itu tidak sama persis dengan default (Tipe). Namun, itu sedekat yang Anda dapatkan tanpa obat generik.
Russell Giddings
8
Terus? Jika Anda menemukan tipe yang default(T) != (T)(object)default(T) && !(default(T) != default(T))kemudian Anda memiliki argumen, jika tidak maka tidak masalah apakah itu kotak atau tidak, karena mereka setara.
Miguel Angelo
7
Bagian terakhir dari predikat adalah untuk menghindari kecurangan dengan operator yang berlebihan ... orang bisa membuat default(T) != default(T)return salah, dan itu curang! =)
Miguel Angelo
4
Ini banyak membantu saya, tetapi saya pikir saya harus menambahkan satu hal yang mungkin berguna bagi beberapa orang yang mencari pertanyaan ini - ada juga metode yang setara jika Anda menginginkan array dari tipe yang diberikan, dan Anda bisa mendapatkannya dengan menggunakan Array.CreateInstance(type, length).
Darrel Hoffman
4
Apakah Anda tidak khawatir membuat instance dari tipe nilai yang tidak dikenal? Ini mungkin memiliki efek jaminan.
ygormutti
103

Mengapa tidak memanggil metode yang mengembalikan default (T) dengan refleksi? Anda dapat menggunakan GetDefault jenis apa pun dengan:

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

    public T GetDefaultGeneric<T>()
    {
        return default(T);
    }
drake7707
sumber
7
Ini brilian karena sangat sederhana. Meskipun ini bukan solusi terbaik di sini, ini adalah solusi penting yang perlu diingat karena teknik ini dapat berguna dalam banyak situasi serupa.
konfigurator
Jika Anda memanggil metode generik "GetDefault" sebagai gantinya (overloading), lakukan ini: this.GetType (). GetMethod ("GetDefault", Tipe baru [0]). <AS_IS>
Stefan Steiger
2
Perlu diingat, implementasi ini jauh lebih lambat (karena refleksi) daripada jawaban yang diterima. Itu masih layak, tetapi Anda harus menyiapkan beberapa caching untuk panggilan GetMethod () / MakeGenericMethod () untuk meningkatkan kinerja.
Doug
1
Ada kemungkinan bahwa argumen tipe tidak berlaku. Misalnya MethodBase.ResultType () dari metode void akan mengembalikan objek Jenis dengan Nama "Void" atau dengan FullName "System.Void". Oleh karena itu saya menjaga: if (t.FullName == "System.Void") mengembalikan null; Terima kasih atas solusinya.
Valo
8
Lebih baik digunakan nameof(GetDefaultGeneric)jika Anda bisa, alih-alih"GetDefaultGeneric"
Mugen
87

Anda bisa menggunakannya PropertyInfo.SetValue(obj, null). Jika dipanggil pada tipe nilai itu akan memberi Anda default. Perilaku ini didokumentasikan dalam. NET 4.0 dan .NET 4.5 .

JoelFan
sumber
7
Untuk pertanyaan khusus ini - pengulangan melalui properti tipe dan mengaturnya ke "default" - ini bekerja dengan sangat baik. Saya menggunakannya ketika mengkonversi dari SqlDataReader ke objek menggunakan refleksi.
Arno Peters
57

Jika Anda menggunakan .NET 4.0 atau di atas dan Anda menginginkan versi program yang bukan kodifikasi aturan yang didefinisikan di luar kode , Anda dapat membuat Expression, mengkompilasi, dan menjalankannya sambil jalan.

Metode ekstensi berikut akan mengambil Typedan mendapatkan nilai yang dikembalikan dari default(T)melalui Defaultmetode di Expressionkelas:

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

Anda juga harus men-cache nilai di atas berdasarkan pada Type, tetapi perlu diketahui jika Anda memanggil ini untuk sejumlah besar Typecontoh, dan jangan menggunakannya terus-menerus, memori yang dikonsumsi oleh cache mungkin lebih besar daripada manfaatnya.

casperOne
sumber
4
Kinerja untuk 'tipe pengembalian. Tipe Harga? Activator.CreateInstance (type): null; ' 1000x lebih cepat dari e.Compile () ();
Cyrus
1
@Cyrus Saya cukup yakin itu akan menjadi sebaliknya jika Anda cache e.Compile(). Itulah inti dari ekspresi.
nawfal
2
Berlari patokan. Jelas, hasil e.Compile()harus di-cache, tetapi dengan asumsi bahwa, metode ini kira-kira 14x lebih cepat untuk misalnya long. Lihat gist.github.com/pvginkel/fed5c8512b9dfefc2870c6853bbfbf8b untuk patokan dan hasil.
Pieter van Ginkel
3
Karena minat, mengapa cache e.Compile()bukan e.Compile()()? mis. Bisakah suatu tipe default berubah pada saat runtime? Jika tidak (seperti yang saya yakini), Anda dapat menyimpan cache hasil daripada ekspresi yang dikompilasi, yang seharusnya meningkatkan kinerja lebih lanjut.
JohnLBevan
3
@JohnLBevan - ya, dan kemudian tidak masalah teknik apa yang Anda gunakan untuk mendapatkan hasilnya - semua akan memiliki kinerja amortisasi yang sangat cepat (pencarian kamus).
Daniel Earwicker
38

Mengapa Anda mengatakan obat generik tidak tersedia?

    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);
    }
Rob Fonseca-Ensor
sumber
Tidak dapat menyelesaikan Metode simbol. Menggunakan PCL untuk Windows.
Cœur
1
berapa mahal untuk membuat metode generik pada waktu berjalan, dan kemudian menggunakannya beberapa ribu kali berturut-turut?
C. Tewalt
1
Saya sedang memikirkan sesuatu seperti ini. Solusi terbaik dan paling elegan untuk saya. Bekerja bahkan pada Compact Framework 2.0. Jika Anda khawatir tentang kinerja, Anda selalu dapat melakukan cache metode generik, bukan?
Bart
Solusi ini sangat tepat! Terima kasih!
Lachezar Lalov
25

Ini adalah solusi Flem yang dioptimalkan:

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;
        }
    }
}
cuft
sumber
2
Versi singkat dari pengembalian:return type.IsValueType ? typeDefaults.GetOrAdd(type, Activator.CreateInstance) : null;
Mark Whitfeld
3
Bagaimana dengan struct yang bisa berubah? Apakah Anda tahu bahwa itu mungkin (dan legal) untuk memodifikasi bidang-bidang dari sebuah kotak struct, sehingga data berubah?
IllidanS4 ingin Monica kembali
@ IllidanS4 sesuai dengan nama metode, ini hanya untuk nilai ValueType default.
aderesh
8

Jawaban yang dipilih adalah jawaban yang baik, tetapi hati-hati dengan objek yang dikembalikan.

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

Mengekstrapolasi ...

string test = GetDefault(typeof(string));
if (test is string)
     Console.WriteLine("This will never be hit.");
BSick7
sumber
14
benar, tetapi itu berlaku untuk default (string) juga, karena setiap jenis referensi lainnya ...
TDaver
string adalah burung aneh - menjadi tipe nilai yang dapat mengembalikan null juga. Jika Anda ingin kode tersebut mengembalikan string.empty, tambahkan saja case khusus untuknya
Dror Helper
15
@Dor - string adalah tipe referensi yang tidak dapat diubah, bukan tipe nilai.
ljs
@ Krono Anda benar - maksud saya string dapat ditangani dengan mengembalikan string.empty atau null sesuai kebutuhan.
Dror Helper
5

Ekspresi dapat membantu di sini:

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

Saya tidak menguji cuplikan ini, tetapi saya pikir itu harus menghasilkan null "diketik" untuk jenis referensi ..

Konstantin Isaev
sumber
1
"typed" nulls- jelaskan. Objek apa yang kamu kembalikan? Jika Anda mengembalikan objek bertipe type, tetapi nilainya adalah null, maka ia tidak - tidak dapat - memiliki informasi lain selain itu null. Anda tidak dapat meminta nullnilai, dan mencari tahu tipe apa yang seharusnya. Jika Anda TIDAK mengembalikan nol, tetapi kembali .. Saya tidak tahu apa .., maka itu tidak akan bertindak seperti null.
ToolmakerSteve
3

Tidak dapat menemukan sesuatu yang sederhana dan elegan dulu, tetapi saya punya satu ide: Jika Anda tahu jenis properti yang ingin Anda atur, Anda dapat menulis sendiri default(T). Ada dua kasus - Tadalah tipe nilai, dan Tmerupakan tipe referensi. Anda dapat melihat ini dengan memeriksa T.IsValueType. Jika Tmerupakan tipe referensi, maka Anda cukup mengaturnya null. Jika Tadalah tipe nilai, maka itu akan memiliki konstruktor tanpa parameter default yang dapat Anda panggil untuk mendapatkan nilai "kosong".

Vilx-
sumber
3

Saya melakukan tugas yang sama seperti ini.

//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;
    }
kpollock
sumber
2

Setara dengan jawaban Dror tetapi sebagai metode ekstensi:

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;
        }
    }
}
Paul Fleming
sumber
2

Penyesuaian sedikit ke solusi @Rob Fonseca-Ensor : Metode ekstensi berikut ini juga berfungsi pada .Net Standard karena saya menggunakan GetRuntimeMethod alih-alih 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);
    }
}

... dan unit test yang sesuai untuk mereka yang peduli tentang kualitas:

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

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

    // Assert
    defaultValue.Should().Be(default(DateTime));
}
thomasgalliker
sumber
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;
    }
Kaz-LA
sumber
2
Tidak berfungsi untuk Nullable<T>tipe: itu tidak mengembalikan yang setara dari default(Nullable<T>)yang seharusnya null. Jawaban yang diterima oleh Dror bekerja lebih baik.
Cœur
dapat memeriksa apakah nullable menggunakan refleksi ...
dancer42
0

Ini seharusnya bekerja: Nullable<T> a = new Nullable<T>().GetValueOrDefault();

penari42
sumber