Ottieni il valore della proprietà dalla stringa usando la riflessione in C #


928

Sto cercando di implementare la trasformazione dei dati usando l' esempio di Reflection 1 nel mio codice.

La GetSourceValuefunzione ha un interruttore che confronta vari tipi, ma voglio rimuovere questi tipi e proprietà e GetSourceValueottenere il valore della proprietà usando solo una singola stringa come parametro. Voglio passare una classe e una proprietà nella stringa e risolvere il valore della proprietà.

È possibile?

1 versione di Web Archive del post originale del blog

Risposte:


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

Naturalmente, vorrai aggiungere la convalida e quant'altro, ma questo è l'essenza.


8
Bello e semplice! Lo renderei generico però:public static T GetPropertyValue<T>(object obj, string propName) { return (T)obj.GetType().GetProperty(propName).GetValue(obj, null); }
Ohad Schneider

2
A un'ottimizzazione può essere rimosso il rischio di un'eccezione nulla come questa: " src.GetType().GetProperty(propName)?.GetValue(src, null);";).
shA.t

8
@ shA.t: penso che sia una cattiva idea. Come si fa a distinguere tra un valore nullo di una proprietà esistente o nessuna proprietà? Preferirei piuttosto sapere immediatamente che stavo inviando un nome di proprietà errato. Questo non è un codice di produzione, ma un miglioramento migliore sarebbe quello di lanciare un'eccezione più specifica (ad es. Verifica null per GetPropertye lancio PropertyNotFoundExceptiono qualcosa se null.)
Ed S.

210

Che ne dici di qualcosa del genere:

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

Questo ti permetterà di scendere nelle proprietà usando una singola stringa, in questo modo:

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

È possibile utilizzare questi metodi come metodi statici o estensioni.


3
@FredJand felice che ti sia imbattuto! È sempre sorprendente quando si presentano questi vecchi post. Era un po 'vago, quindi ho aggiunto un po' di testo per spiegarlo. Ho anche iniziato a utilizzare questi come metodi di estensione e ho aggiunto un modulo generico, quindi l'ho aggiunto qui.
jheddings

Perché la guardia nulla è nella foreach e non sopra?
Santhos,

4
@Santhos poiché 'obj' viene ridefinito nel corpo del ciclo foreach, viene controllato durante ogni iterazione.
jheddings,

Utile, ma nel caso limite che una delle proprietà nidificate potrebbe essere nascosta (utilizzando il modificatore "nuovo"), verrà generata un'eccezione per la ricerca di proprietà duplicate. Sarebbe più ordinato tenere traccia dell'ultimo tipo di proprietà e utilizzarlo PropertyInfo.PropertyTypeanziché obj.GetType()su proprietà nidificate, proprio come accederebbe alla proprietà su una proprietà nidificata.
Nullius,

È possibile utilizzare l' nameofespressione da C # 6 in questo modo: nameof(TimeOfDay.Minutes)sul parametro name quando si chiama la funzione per eliminare le stringhe magiche e aggiungere la sicurezza del tempo di compilazione a queste chiamate.
Raccogli

74

Aggiungi a qualsiasi 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; }
}

Quindi, puoi usare come:

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

@EduardoCuomo: è possibile usare la riflessione in questo modo, quindi non è necessario sapere quali membri della classe ha?
Our Man in Bananas,

È possibile farlo se "Bar" fosse un oggetto?
big_water

@big_water funziona con i metodi SetValuee . Se devi lavorare con un tipo specifico, dovresti trasmettere il risultato e lanciare il valore con cui assegnarloGetValueObjectGetValueSetValue
Eduardo Cuomo,

Scusa @OurManinBananas, non riesco a capire la tua domanda. Che cosa vuoi fare?
Eduardo Cuomo,

Qual è il nome di questo tipo di metodi ..?
Sahan Chinthaka,

45

Che ne dici di usare il CallByNameof of Microsoft.VisualBasicnamespace ( Microsoft.VisualBasic.dll)? Utilizza la riflessione per ottenere proprietà, campi e metodi di oggetti normali, oggetti COM e persino oggetti dinamici.

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

e poi

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

5
Suggerimento interessante, un'ulteriore ispezione ha dimostrato che è in grado di gestire sia i campi che le proprietà, gli oggetti COM e può persino gestire un'associazione dinamica corretta !
IllidanS4 vuole Monica indietro il

Ricevo un errore: membro pubblico "MyPropertyName" sul tipo "MyType" non trovato.
vldmrrdjcc,

30

Ottima risposta dai matrimoni. Vorrei migliorarlo consentendo il riferimento di matrici aggregate o raccolte di oggetti, in modo che propertyName possa essere 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;
    }

che dire di un elenco di elenchi accessibili da MasterList [0] [1]?
Jesse Adam,

as Array -> as object [] comporta anche un'eccezione Nullreference. Ciò che funziona per me (metodo prop non più efficiente), è lanciare unknownCollection su IEnumerable e utilizzare ToArray () sul risultato. violino
Jeroen Jonkman,

14

Se uso il codice di Ed S. ottengo

'ReflectionExtensions.GetProperty (Type, string)' è inaccessibile a causa del suo livello di protezione

Sembra che GetProperty()non sia disponibile in Xamarin.Forms. TargetFrameworkProfileè Profile7nella mia Portable Class Library (.NET Framework 4.5, Windows 8, ASP.NET Core 1.0, Xamarin.Android, Xamarin.iOS, Xamarin.iOS Classic).

Ora ho trovato una soluzione funzionante:

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

fonte


4
Solo un piccolo possibile miglioramento. Sostituisci IF e il prossimo ritorno con: return property? .GetValue (source);
Tomino,

11

Informazioni sulla discussione sulle proprietà nidificate, è possibile evitare tutte le cose di riflessione se si utilizza il DataBinder.Eval Method (Object, String)seguente:

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

Ovviamente, dovrai aggiungere un riferimento System.Weball'assembly, ma questo probabilmente non è un grosso problema.


8

Il metodo da chiamare è cambiato in .NET Standard (a partire dalla 1.6). Inoltre possiamo usare l'operatore condizionale nullo di C # 6.

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

1
per usare il? operator
blfuentes il

4

Utilizzo di PropertyInfo dello spazio dei nomi System.Reflection . Reflection viene compilato correttamente, indipendentemente dalla proprietà a cui tentiamo di accedere. L'errore verrà visualizzato durante il runtime.

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

Funziona bene per ottenere la proprietà Location di un oggetto

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

Otterremo la posizione: {X = 71, Y = 27} Possiamo anche restituire location.X o location.Y allo stesso modo.


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

Questo è un modo per ottenere tutte le proprietà con i loro valori in un elenco.


Perché lo stanno facendo: type.GetProperty(pi.Name)quando è == alla variabile pi?
Weston,

Se stai usando c # 6.0, sbarazzati di ife fai selfValue?.ToString()Altrimenti liberati di ife usaselfValue==null?null:selfValue.ToString()
weston

Anche un elenco di List<KeyValuePair<è strano, usa un dizionarioDictionary<string, string>
Weston

3

Il codice seguente è un metodo ricorsivo per visualizzare l'intera gerarchia di tutti i nomi e valori delle proprietà contenuti nell'istanza di un oggetto. Questo metodo utilizza una versione semplificata della GetPropertyValue()risposta di AlexD sopra in questo thread. Grazie a questo thread di discussione, sono stato in grado di capire come farlo!

Ad esempio, utilizzo questo metodo per mostrare un'esplosione o un dump di tutte le proprietà in una WebServicerisposta chiamando il metodo come segue:

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

Il metodo seguente funziona perfettamente per me:

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

Per ottenere il valore della proprietà:

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

Per impostare il valore della proprietà:

t1["prop1"] = value;

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

2

Ecco un altro modo per trovare una proprietà nidificata che non richiede la stringa per dirti il ​​percorso di nidificazione. Ringraziamo Ed S. per il metodo della singola proprietà.

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

Potrebbe essere meglio verificare se Type.GetPropertyrestituisce null invece di chiamare GetValuee averlo NullReferenceExceptiongettato in un ciclo.
Groo,

2

Non menzionerai mai quale oggetto stai ispezionando, e poiché stai rifiutando quelli che fanno riferimento a un determinato oggetto, supporrò che tu intenda uno statico.

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

Si noti che ho contrassegnato l'oggetto che viene ispezionato con la variabile locale obj. nullsignifica statico, altrimenti impostalo su quello che vuoi. Si noti inoltre che GetEntryAssembly()è uno dei pochi metodi disponibili per ottenere l'assembly "in esecuzione", è possibile che si desideri giocarci attorno se si fatica a caricare il tipo.


2

Dai un'occhiata alla libreria Heleonix.Reflection . È possibile ottenere / impostare / invocare membri per percorsi o creare un getter / setter (lambda compilato in un delegato) che è più veloce della riflessione. Per esempio:

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

O creare una volta getter e cache per il riutilizzo (questo è più performante ma potrebbe generare NullReferenceException se un membro intermedio è null):

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

Oppure, se si desidera creare un List<Action<object, object>>diverso getter, è sufficiente specificare i tipi di base per i delegati compilati (le conversioni di tipo verranno aggiunte in lambda compilati):

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

1
non utilizzare mai librerie di terze parti, se è possibile implementarlo nel proprio codice in un tempo ragionevole in 5-10 righe.
Artem G

1

modo più breve ....

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 e AlexD hanno entrambi scritto eccellenti risposte su come risolvere le stringhe di proprietà. Vorrei buttare la mia nel mix, dal momento che ho scritto una libreria dedicata proprio a tale scopo.

La classe principale di Pather.CSharp èResolver. Per impostazione predefinita, può risolvere proprietà, array e voci del dizionario.

Quindi, ad esempio, se hai un oggetto come questo

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

e vuoi farlo Property2, puoi farlo in questo modo:

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

Questo è l'esempio più semplice dei percorsi che può risolvere. Se vuoi vedere cos'altro può o come estenderlo, vai alla sua pagina Github .


0

Ecco la mia soluzione Funziona anche con oggetti COM e consente di accedere a oggetti di raccolta / array da oggetti 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

Ecco cosa ho ottenuto sulla base di altre risposte. Un po 'eccessivo nel diventare così specifico con la gestione degli errori.

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);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.