Dapatkan nilai properti dari string menggunakan refleksi di C #


928

Saya mencoba menerapkan transformasi data menggunakan contoh Reflection 1 dalam kode saya.

The GetSourceValuefungsi memiliki saklar membandingkan berbagai jenis, tapi saya ingin menghapus jenis dan sifat dan memiliki GetSourceValuemendapatkan nilai dari properti hanya menggunakan satu string sebagai parameter. Saya ingin meneruskan kelas dan properti di string dan menyelesaikan nilai properti.

Apakah ini mungkin?

1 versi Web Archive dari posting blog asli

Jawaban:


1793
 public static object GetPropValue(object src, string propName)
 {
     return src.GetType().GetProperty(propName).GetValue(src, null);
 }

Tentu saja, Anda ingin menambahkan validasi dan yang lainnya, tetapi itulah intinya.


8
Bagus dan sederhana! Saya akan membuatnya generik:public static T GetPropertyValue<T>(object obj, string propName) { return (T)obj.GetType().GetProperty(propName).GetValue(obj, null); }
Ohad Schneider

2
Pengoptimalan dapat menghapus risiko pengecualian nol seperti ini: " src.GetType().GetProperty(propName)?.GetValue(src, null);";).
shA.t

8
@ shA.t: Saya pikir itu ide yang buruk. Bagaimana Anda membedakan antara nilai nol dari properti yang ada atau tidak ada properti sama sekali? Saya lebih suka segera tahu bahwa saya mengirim nama properti yang buruk. Ini bukan kode produksi, tetapi peningkatan yang lebih baik adalah dengan melempar pengecualian yang lebih spesifik (mis. Periksa untuk null on GetPropertydan throw PropertyNotFoundExceptionatau sesuatu jika null.)
Ed S.

210

Bagaimana dengan sesuatu yang seperti ini:

public static Object GetPropValue(this Object obj, String name) {
    foreach (String part in name.Split('.')) {
        if (obj == null) { return null; }

        Type type = obj.GetType();
        PropertyInfo info = type.GetProperty(part);
        if (info == null) { return null; }

        obj = info.GetValue(obj, null);
    }
    return obj;
}

public static T GetPropValue<T>(this Object obj, String name) {
    Object retval = GetPropValue(obj, name);
    if (retval == null) { return default(T); }

    // throws InvalidCastException if types are incompatible
    return (T) retval;
}

Ini akan memungkinkan Anda untuk turun ke properti menggunakan string tunggal, seperti ini:

DateTime now = DateTime.Now;
int min = GetPropValue<int>(now, "TimeOfDay.Minutes");
int hrs = now.GetPropValue<int>("TimeOfDay.Hours");

Anda dapat menggunakan metode ini sebagai metode statis atau ekstensi.


3
@FredJand senang Anda menemukan itu! Itu selalu mengejutkan ketika posting lama ini muncul. Itu agak kabur sehingga saya menambahkan sedikit teks untuk menjelaskannya. Saya juga telah beralih menggunakan ini sebagai metode ekstensi dan menambahkan formulir generik, jadi saya menambahkannya di sini.
jheddings

Mengapa penjaga nol di foreach dan tidak di atas?
Santhos

4
@Santhos karena 'obj' didefinisikan ulang dalam tubuh loop foreach, itu diperiksa selama setiap iterasi.
jheddings

Berguna, tetapi dalam kasus tepi bahwa salah satu properti bersarang mungkin disembunyikan (menggunakan pengubah 'baru'), itu akan membuang pengecualian untuk menemukan properti duplikat. Akan lebih rapi untuk melacak tipe properti terakhir dan menggunakan PropertyInfo.PropertyTypealih-alih obj.GetType()pada properti bersarang, seperti halnya mengakses properti pada properti bersarang.
Nullius

Anda dapat menggunakan nameofekspresi pada C # 6 seperti ini: nameof(TimeOfDay.Minutes)pada parameter nama saat memanggil fungsi untuk menghilangkan string ajaib dan menambahkan kompilasi waktu aman untuk panggilan ini.
Menuai

74

Tambahkan ke sembarang Class:

public class Foo
{
    public object this[string propertyName]
    {
        get { return this.GetType().GetProperty(propertyName).GetValue(this, null); }
        set { this.GetType().GetProperty(propertyName).SetValue(this, value, null); }
    }

    public string Bar { get; set; }
}

Kemudian, Anda dapat menggunakan sebagai:

Foo f = new Foo();
// Set
f["Bar"] = "asdf";
// Get
string s = (string)f["Bar"];

@EduardoCuomo: Apakah mungkin menggunakan refleksi dengan ini sehingga Anda tidak perlu tahu apa yang dimiliki anggota kelas?
Manusia Kita di Pisang

Apakah mungkin untuk melakukan ini jika "Bar" adalah objek?
big_water

@big_water metode SetValuedan GetValuebekerja dengan Object. Jika Anda perlu bekerja dengan jenis tertentu, Anda harus memberikan hasil GetValuedan memberikan nilai untuk memberikannyaSetValue
Eduardo Cuomo

Maaf @OurManinBananas, saya tidak dapat memahami pertanyaan Anda. Apa yang ingin kamu lakukan?
Eduardo Cuomo

Apa nama metode jenis ini ..?
Sahan Chinthaka

45

Bagaimana menggunakan CallByNamesatu Microsoft.VisualBasicnamespace ( Microsoft.VisualBasic.dll)? Ini menggunakan refleksi untuk mendapatkan properti, bidang, dan metode objek normal, objek COM, dan bahkan objek dinamis.

using Microsoft.VisualBasic;
using Microsoft.VisualBasic.CompilerServices;

lalu

Versioned.CallByName(this, "method/function/prop name", CallType.Get).ToString();

5
Saran yang menarik, pemeriksaan lebih lanjut membuktikan bahwa ia dapat menangani bidang dan properti, objek COM, dan bahkan dapat menangani pengikatan dinamis yang benar !
IllidanS4 ingin Monica kembali pada

Saya mendapatkan kesalahan: Anggota publik 'MyPropertyName' pada tipe 'MyType' tidak ditemukan.
vldmrrdjcc

30

Jawaban yang bagus oleh jheddings. Saya ingin memperbaikinya memungkinkan referensi array agregat atau koleksi objek, sehingga propertyName bisa menjadi property1.property2 [X] .property3:

    public static object GetPropertyValue(object srcobj, string propertyName)
    {
        if (srcobj == null)
            return null;

        object obj = srcobj;

        // Split property name to parts (propertyName could be hierarchical, like obj.subobj.subobj.property
        string[] propertyNameParts = propertyName.Split('.');

        foreach (string propertyNamePart in propertyNameParts)
        {
            if (obj == null)    return null;

            // propertyNamePart could contain reference to specific 
            // element (by index) inside a collection
            if (!propertyNamePart.Contains("["))
            {
                PropertyInfo pi = obj.GetType().GetProperty(propertyNamePart);
                if (pi == null) return null;
                obj = pi.GetValue(obj, null);
            }
            else
            {   // propertyNamePart is areference to specific element 
                // (by index) inside a collection
                // like AggregatedCollection[123]
                //   get collection name and element index
                int indexStart = propertyNamePart.IndexOf("[")+1;
                string collectionPropertyName = propertyNamePart.Substring(0, indexStart-1);
                int collectionElementIndex = Int32.Parse(propertyNamePart.Substring(indexStart, propertyNamePart.Length-indexStart-1));
                //   get collection object
                PropertyInfo pi = obj.GetType().GetProperty(collectionPropertyName);
                if (pi == null) return null;
                object unknownCollection = pi.GetValue(obj, null);
                //   try to process the collection as array
                if (unknownCollection.GetType().IsArray)
                {
                    object[] collectionAsArray = unknownCollection as object[];
                    obj = collectionAsArray[collectionElementIndex];
                }
                else
                {
                    //   try to process the collection as IList
                    System.Collections.IList collectionAsList = unknownCollection as System.Collections.IList;
                    if (collectionAsList != null)
                    {
                        obj = collectionAsList[collectionElementIndex];
                    }
                    else
                    {
                        // ??? Unsupported collection type
                    }
                }
            }
        }

        return obj;
    }

bagaimana dengan Daftar Daftar yang diakses oleh MasterList [0] [1]?
Jesse Adam

sebagai Array -> as object [] juga menghasilkan pengecualian Nullreference. Apa yang bekerja untuk saya (prop bukan metode yang paling efisien), adalah untuk melemparkan unknownCollection ke IEnumerable dan daripada menggunakan ToArray () pada hasilnya. biola
Jeroen Jonkman

14

Jika saya menggunakan kode dari Ed S. saya dapatkan

'ReflectionExtensions.GetProperty (Type, string)' tidak dapat diakses karena tingkat perlindungannya

Tampaknya GetProperty()tidak tersedia di Xamarin. Bentuk. TargetFrameworkProfileada Profile7di Portable Class Library saya (.NET Framework 4.5, Windows 8, ASP.NET Core 1.0, Xamarin. Android, Xamarin.iOS, Xamarin.iOS Classic).

Sekarang saya menemukan solusi yang berfungsi:

using System.Linq;
using System.Reflection;

public static object GetPropValue(object source, string propertyName)
{
    var property = source.GetType().GetRuntimeProperties().FirstOrDefault(p => string.Equals(p.Name, propertyName, StringComparison.OrdinalIgnoreCase));
    return property?.GetValue(source);
}

Sumber


4
Hanya peningkatan kecil yang mungkin terjadi. Ganti IF dan pengembalian berikutnya dengan: return property? .GetValue (sumber);
Tomino

11

Tentang diskusi properti bersarang, Anda dapat menghindari semua hal refleksi jika Anda menggunakan DataBinder.Eval Method (Object, String)seperti di bawah ini:

var value = DataBinder.Eval(DateTime.Now, "TimeOfDay.Hours");

Tentu saja, Anda perlu menambahkan referensi ke System.Webmajelis, tetapi ini mungkin bukan masalah besar.


8

Metode untuk menelepon telah berubah dalam .NET Standard (pada 1.6). Kita juga bisa menggunakan operator bersyarat nol C # 6.

using System.Reflection; 
public static object GetPropValue(object src, string propName)
{
    return src.GetType().GetRuntimeProperty(propName)?.GetValue(src);
}

1
untuk menggunakan? operator
blfuentes

4

Menggunakan PropertyInfo dari System.Reflection namespace. Refleksi mengkompilasi dengan baik apa pun properti yang kami coba akses. Kesalahan akan muncul saat run-time.

    public static object GetObjProperty(object obj, string property)
    {
        Type t = obj.GetType();
        PropertyInfo p = t.GetProperty("Location");
        Point location = (Point)p.GetValue(obj, null);
        return location;
    }

Ini berfungsi dengan baik untuk mendapatkan properti Lokasi objek

Label1.Text = GetObjProperty(button1, "Location").ToString();

Kami akan mendapatkan Lokasi: {X = 71, Y = 27} Kami juga dapat mengembalikan location.X atau location.Y dengan cara yang sama.


4
public static List<KeyValuePair<string, string>> GetProperties(object item) //where T : class
    {
        var result = new List<KeyValuePair<string, string>>();
        if (item != null)
        {
            var type = item.GetType();
            var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            foreach (var pi in properties)
            {
                var selfValue = type.GetProperty(pi.Name).GetValue(item, null);
                if (selfValue != null)
                {
                    result.Add(new KeyValuePair<string, string>(pi.Name, selfValue.ToString()));
                }
                else
                {
                    result.Add(new KeyValuePair<string, string>(pi.Name, null));
                }
            }
        }
        return result;
    }

Ini adalah cara untuk mendapatkan semua properti dengan nilainya dalam Daftar.


Mengapa melakukan ini: type.GetProperty(pi.Name)ketika itu == ke variabel pi?
barat

Jika Anda menggunakan c # 6.0, singkirkan ifdan lakukan selfValue?.ToString()Jika tidak singkirkan ifdan gunakanselfValue==null?null:selfValue.ToString()
weston

Juga daftar List<KeyValuePair<aneh, gunakan kamusDictionary<string, string>
weston

3

Kode berikut adalah metode Rekursif untuk menampilkan seluruh hierarki dari semua Nama dan Nilai Properti yang terkandung dalam instance objek. Metode ini menggunakan versi sederhana dari GetPropertyValue()jawaban AlexD di atas di utas ini. Berkat utas diskusi ini, saya bisa mengetahui bagaimana melakukan ini!

Sebagai contoh, saya menggunakan metode ini untuk menampilkan ledakan atau pembuangan semua properti dalam WebServicerespons dengan memanggil metode sebagai berikut:

PropertyValues_byRecursion("Response", response, false);

public static object GetPropertyValue(object srcObj, string propertyName)
{
  if (srcObj == null) 
  {
    return null; 
  }
  PropertyInfo pi = srcObj.GetType().GetProperty(propertyName.Replace("[]", ""));
  if (pi == null)
  {
    return null;
  }
  return pi.GetValue(srcObj);
}

public static void PropertyValues_byRecursion(string parentPath, object parentObj, bool showNullValues)
{
  /// Processes all of the objects contained in the parent object.
  ///   If an object has a Property Value, then the value is written to the Console
  ///   Else if the object is a container, then this method is called recursively
  ///       using the current path and current object as parameters

  // Note:  If you do not want to see null values, set showNullValues = false

  foreach (PropertyInfo pi in parentObj.GetType().GetTypeInfo().GetProperties())
  {
    // Build the current object property's namespace path.  
    // Recursion extends this to be the property's full namespace path.
    string currentPath = parentPath + "." + pi.Name;

    // Get the selected property's value as an object
    object myPropertyValue = GetPropertyValue(parentObj, pi.Name);
    if (myPropertyValue == null)
    {
      // Instance of Property does not exist
      if (showNullValues)
      {
        Console.WriteLine(currentPath + " = null");
        // Note: If you are replacing these Console.Write... methods callback methods,
        //       consider passing DBNull.Value instead of null in any method object parameters.
      }
    }
    else if (myPropertyValue.GetType().IsArray)
    {
      // myPropertyValue is an object instance of an Array of business objects.
      // Initialize an array index variable so we can show NamespacePath[idx] in the results.
      int idx = 0;
      foreach (object business in (Array)myPropertyValue)
      {
        if (business == null)
        {
          // Instance of Property does not exist
          // Not sure if this is possible in this context.
          if (showNullValues)
          {
            Console.WriteLine(currentPath  + "[" + idx.ToString() + "]" + " = null");
          }
        }
        else if (business.GetType().IsArray)
        {
          // myPropertyValue[idx] is another Array!
          // Let recursion process it.
          PropertyValues_byRecursion(currentPath + "[" + idx.ToString() + "]", business, showNullValues);
        }
        else if (business.GetType().IsSealed)
        {
          // Display the Full Property Path and its Value
          Console.WriteLine(currentPath + "[" + idx.ToString() + "] = " + business.ToString());
        }
        else
        {
          // Unsealed Type Properties can contain child objects.
          // Recurse into my property value object to process its properties and child objects.
          PropertyValues_byRecursion(currentPath + "[" + idx.ToString() + "]", business, showNullValues);
        }
        idx++;
      }
    }
    else if (myPropertyValue.GetType().IsSealed)
    {
      // myPropertyValue is a simple value
      Console.WriteLine(currentPath + " = " + myPropertyValue.ToString());
    }
    else
    {
      // Unsealed Type Properties can contain child objects.
      // Recurse into my property value object to process its properties and child objects.
      PropertyValues_byRecursion(currentPath, myPropertyValue, showNullValues);
    }
  }
}

3
public static TValue GetFieldValue<TValue>(this object instance, string name)
{
    var type = instance.GetType(); 
    var field = type.GetFields(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance).FirstOrDefault(e => typeof(TValue).IsAssignableFrom(e.FieldType) && e.Name == name);
    return (TValue)field?.GetValue(instance);
}

public static TValue GetPropertyValue<TValue>(this object instance, string name)
{
    var type = instance.GetType();
    var field = type.GetProperties(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance).FirstOrDefault(e => typeof(TValue).IsAssignableFrom(e.PropertyType) && e.Name == name);
    return (TValue)field?.GetValue(instance);
}

3
public class YourClass
{
    //Add below line in your class
    public object this[string propertyName] => GetType().GetProperty(propertyName)?.GetValue(this, null);
    public string SampleProperty { get; set; }
}

//And you can get value of any property like this.
var value = YourClass["SampleProperty"];

3

Metode di bawah ini berfungsi sempurna untuk saya:

class MyClass {
    public string prop1 { set; get; }

    public object this[string propertyName]
    {
        get { return this.GetType().GetProperty(propertyName).GetValue(this, null); }
        set { this.GetType().GetProperty(propertyName).SetValue(this, value, null); }
    }
}

Untuk mendapatkan nilai properti:

MyClass t1 = new MyClass();
...
string value = t1["prop1"].ToString();

Untuk mengatur nilai properti:

t1["prop1"] = value;

2
Dim NewHandle As YourType = CType(Microsoft.VisualBasic.CallByName(ObjectThatContainsYourVariable, "YourVariableName", CallType), YourType)

2

Berikut ini cara lain untuk menemukan properti bersarang yang tidak memerlukan string untuk memberi tahu Anda jalan bersarang. Kredit untuk Ed S. untuk metode properti tunggal.

    public static T FindNestedPropertyValue<T, N>(N model, string propName) {
        T retVal = default(T);
        bool found = false;

        PropertyInfo[] properties = typeof(N).GetProperties();

        foreach (PropertyInfo property in properties) {
            var currentProperty = property.GetValue(model, null);

            if (!found) {
                try {
                    retVal = GetPropValue<T>(currentProperty, propName);
                    found = true;
                } catch { }
            }
        }

        if (!found) {
            throw new Exception("Unable to find property: " + propName);
        }

        return retVal;
    }

        public static T GetPropValue<T>(object srcObject, string propName) {
        return (T)srcObject.GetType().GetProperty(propName).GetValue(srcObject, null);
    }

Mungkin lebih baik untuk memeriksa apakah Type.GetPropertykembali null daripada menelepon GetValuedan NullReferenceExceptionmelemparkan dalam satu lingkaran.
Groo

2

Anda tidak pernah menyebutkan objek apa yang Anda periksa, dan karena Anda menolak objek yang merujuk objek tertentu, saya akan menganggap Anda maksud objek statis.

using System.Reflection;
public object GetPropValue(string prop)
{
    int splitPoint = prop.LastIndexOf('.');
    Type type = Assembly.GetEntryAssembly().GetType(prop.Substring(0, splitPoint));
    object obj = null;
    return type.GetProperty(prop.Substring(splitPoint + 1)).GetValue(obj, null);
}

Perhatikan bahwa saya menandai objek yang sedang diperiksa dengan variabel lokal obj. nullartinya statis, jika tidak, atur sesuai keinginan Anda. Perhatikan juga bahwa GetEntryAssembly()ini adalah salah satu dari beberapa metode yang tersedia untuk mendapatkan rakitan "berjalan", Anda mungkin ingin bermain-main dengannya jika Anda mengalami kesulitan memuat jenis.


2

Silahkan lihat di Heleonix.Reflection perpustakaan. Anda dapat memperoleh / mengatur / meminta anggota melalui jalur, atau membuat pengambil / penyetel (lambda dikompilasi menjadi delegasi) yang lebih cepat dari refleksi. Sebagai contoh:

var success = Reflector.Get(DateTime.Now, null, "Date.Year", out int value);

Atau buat pengambil dan tembolok untuk digunakan kembali (ini lebih berkinerja tetapi mungkin melempar NullReferenceException jika anggota perantara adalah nol):

var getter = Reflector.CreateGetter<DateTime, int>("Date.Year", typeof(DateTime));
getter(DateTime.Now);

Atau jika Anda ingin membuat List<Action<object, object>>getter yang berbeda, cukup tentukan tipe dasar untuk delegasi yang dikompilasi (konversi tipe akan ditambahkan ke dalam lambda yang dikompilasi):

var getter = Reflector.CreateGetter<object, object>("Date.Year", typeof(DateTime));
getter(DateTime.Now);

1
jangan pernah menggunakan lib pihak ke-3, jika Anda dapat menerapkannya dalam kode Anda sendiri dalam waktu yang wajar dalam 5-10 baris.
Artem G

1

cara yang lebih pendek ....

var a = new Test { Id = 1 , Name = "A" , date = DateTime.Now};
var b = new Test { Id = 1 , Name = "AXXX", date = DateTime.Now };

var compare = string.Join("",a.GetType().GetProperties().Select(x => x.GetValue(a)).ToArray())==
              string.Join("",b.GetType().GetProperties().Select(x => x.GetValue(b)).ToArray());

1

jheddings dan AlexD keduanya menulis jawaban yang sangat baik tentang cara mengatasi string properti. Saya ingin melempar milik saya ke dalam campuran, karena saya menulis perpustakaan khusus untuk tujuan itu.

Kelas utama Pather.CSharp adalahResolver. Per default itu dapat menyelesaikan entri properti, array dan kamus.

Jadi, misalnya, jika Anda memiliki objek seperti ini

var o = new { Property1 = new { Property2 = "value" } };

dan ingin mendapatkan Property2, Anda bisa melakukannya seperti ini:

IResolver resolver = new Resolver();
var path = "Property1.Property2";
object result = r.Resolve(o, path); 
//=> "value"

Ini adalah contoh paling mendasar dari jalur yang dapat diselesaikannya. Jika Anda ingin melihat apa lagi yang bisa dilakukan, atau bagaimana Anda bisa memperpanjangnya, cukup buka halaman Github -nya .


0

Ini solusinya. Ia bekerja juga dengan objek COM dan memungkinkan untuk mengakses koleksi / array item dari objek COM.

public static object GetPropValue(this object obj, string name)
{
    foreach (string part in name.Split('.'))
    {
        if (obj == null) { return null; }

        Type type = obj.GetType();
        if (type.Name == "__ComObject")
        {
            if (part.Contains('['))
            {
                string partWithoundIndex = part;
                int index = ParseIndexFromPropertyName(ref partWithoundIndex);
                obj = Versioned.CallByName(obj, partWithoundIndex, CallType.Get, index);
            }
            else
            {
                obj = Versioned.CallByName(obj, part, CallType.Get);
            }
        }
        else
        {
            PropertyInfo info = type.GetProperty(part);
            if (info == null) { return null; }
            obj = info.GetValue(obj, null);
        }
    }
    return obj;
}

private static int ParseIndexFromPropertyName(ref string name)
{
    int index = -1;
    int s = name.IndexOf('[') + 1;
    int e = name.IndexOf(']');
    if (e < s)
    {
        throw new ArgumentException();
    }
    string tmp = name.Substring(s, e - s);
    index = Convert.ToInt32(tmp);
    name = name.Substring(0, s - 1);
    return index;
}

0

Inilah yang saya dapatkan berdasarkan jawaban lain. Sedikit berlebihan untuk menjadi sangat spesifik dengan penanganan kesalahan.

public static T GetPropertyValue<T>(object sourceInstance, string targetPropertyName, bool throwExceptionIfNotExists = false)
{
    string errorMsg = null;

    try
    {
        if (sourceInstance == null || string.IsNullOrWhiteSpace(targetPropertyName))
        {
            errorMsg = $"Source object is null or property name is null or whitespace. '{targetPropertyName}'";
            Log.Warn(errorMsg);

            if (throwExceptionIfNotExists)
                throw new ArgumentException(errorMsg);
            else
                return default(T);
        }

        Type returnType = typeof(T);
        Type sourceType = sourceInstance.GetType();

        PropertyInfo propertyInfo = sourceType.GetProperty(targetPropertyName, returnType);
        if (propertyInfo == null)
        {
            errorMsg = $"Property name '{targetPropertyName}' of type '{returnType}' not found for source object of type '{sourceType}'";
            Log.Warn(errorMsg);

            if (throwExceptionIfNotExists)
                throw new ArgumentException(errorMsg);
            else
                return default(T);
        }

        return (T)propertyInfo.GetValue(sourceInstance, null);
    }
    catch(Exception ex)
    {
        errorMsg = $"Problem getting property name '{targetPropertyName}' from source instance.";
        Log.Error(errorMsg, ex);

        if (throwExceptionIfNotExists)
            throw;
    }

    return default(T);
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.