Obtenir la valeur d'une propriété à partir d'une chaîne à l'aide de la réflexion en C #


928

J'essaie d'implémenter la transformation de données à l'aide de l' exemple Reflection 1 dans mon code.

La GetSourceValuefonction a un commutateur comparant différents types, mais je veux supprimer ces types et propriétés et GetSourceValueobtenir la valeur de la propriété en utilisant une seule chaîne comme paramètre. Je veux passer une classe et une propriété dans la chaîne et résoudre la valeur de la propriété.

Est-ce possible?

1 version Web Archive de l'article de blog original

Réponses:


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

Bien sûr, vous voudrez ajouter la validation et ainsi de suite, mais c'est l'essentiel.


8
Agréable et simple! Je le rendrais générique cependant:public static T GetPropertyValue<T>(object obj, string propName) { return (T)obj.GetType().GetProperty(propName).GetValue(obj, null); }
Ohad Schneider

2
Une optimisation peut supprimer le risque d'exception nulle comme ceci: " src.GetType().GetProperty(propName)?.GetValue(src, null);";).
shA.t

8
@ shA.t: Je pense que c'est une mauvaise idée. Comment différenciez-vous une valeur nulle d'une propriété existante ou aucune propriété du tout? Je préfère de loin savoir immédiatement que j'envoyais un mauvais nom de propriété. Ce n'est pas du code de production, mais une meilleure amélioration serait de lever une exception plus spécifique (par exemple, vérifier la valeur null on GetPropertyand throw PropertyNotFoundExceptionou quelque chose si null.)
Ed S.

210

Que diriez-vous quelque chose comme ça:

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

Cela vous permettra de descendre dans les propriétés en utilisant une seule chaîne, comme ceci:

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

Vous pouvez utiliser ces méthodes comme des méthodes statiques ou des extensions.


3
@FredJand heureux que vous soyez tombé dessus! C'est toujours surprenant quand ces anciens messages arrivent. C'était un peu vague alors j'ai ajouté un peu de texte pour l'expliquer. J'ai également opté pour ces méthodes d'extension et ajouté un formulaire générique, je l'ai donc ajouté ici.
jheddings

Pourquoi la garde nulle dans le foreach et pas au-dessus?
Santhos

4
@Santhos puisque 'obj' est redéfini dans le corps de la boucle foreach, il est vérifié à chaque itération.
jheddings

Utile, mais dans le cas où une des propriétés imbriquées pourrait être cachée (en utilisant le modificateur «nouveau»), elle lèvera une exception pour trouver des propriétés en double. Il serait plus pratique de garder une trace du dernier type de propriété et de l'utiliser PropertyInfo.PropertyTypeau lieu de obj.GetType()sur des propriétés imbriquées, tout comme l'accès à la propriété sur une propriété imbriquée.
Nullius

Vous pouvez utiliser l' nameofexpression à partir de C # 6 comme ceci: nameof(TimeOfDay.Minutes)sur le paramètre name lors de l'appel de la fonction pour débarrasser les chaînes magiques et ajouter une sécurité de temps de compilation à ces appels.
Reap

74

Ajoutez à tout 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; }
}

Ensuite, vous pouvez utiliser comme:

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

@EduardoCuomo: Est-il possible d'utiliser la réflexion avec cela pour que vous n'ayez pas besoin de savoir quels membres la classe a?
Our Man in Bananas

Est-il possible de faire cela si "Bar" était un objet?
big_water

@big_water avec les méthodes SetValueet GetValuefonctionne Object. Si vous avez besoin de travailler avec un type spécifique, vous devez lancer le résultat de GetValueet convertir la valeur pour l'attribuer avecSetValue
Eduardo Cuomo

Désolé @OurManinBananas, je ne comprends pas votre question. Qu'est-ce que tu veux faire?
Eduardo Cuomo

Quel est le nom de ce type de méthodes ..?
Sahan Chinthaka

45

Qu'en est-il de l'utilisation CallByNamede l' Microsoft.VisualBasicespace de noms ( Microsoft.VisualBasic.dll)? Il utilise la réflexion pour obtenir les propriétés, les champs et les méthodes des objets normaux, des objets COM et même des objets dynamiques.

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

et alors

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

5
Suggestion intéressante, une inspection approfondie a prouvé qu'il peut gérer à la fois les champs et les propriétés, les objets COM, et qu'il peut même gérer correctement la liaison dynamique !
IllidanS4 veut que Monica revienne

Je reçois une erreur: le membre public «MyPropertyName» sur le type «MyType» est introuvable.
vldmrrdjcc

30

Grande réponse par jheddings. Je voudrais l'améliorer en permettant le référencement de tableaux agrégés ou de collections d'objets, afin que propertyName puisse être 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;
    }

qu'en est-il d'une liste de listes accessible par MasterList [0] [1]?
Jesse Adam

as Array -> as object [] entraîne également une exception Nullreference. Ce qui fonctionne pour moi (méthode la plus efficace), c'est de lancer unknownCollection en IEnumerable et d'utiliser ToArray () sur le résultat. violon
Jeroen Jonkman

14

Si j'utilise le code d' Ed S. j'obtiens

'ReflectionExtensions.GetProperty (Type, string)' est inaccessible en raison de son niveau de protection

Il semble que ce GetProperty()ne soit pas disponible dans Xamarin.Forms. TargetFrameworkProfileestProfile7 dans ma bibliothèque de classes portable (.NET Framework 4.5, Windows 8, ASP.NET Core 1.0, Xamarin.Android, Xamarin.iOS, Xamarin.iOS Classic).

Maintenant, j'ai trouvé une solution de travail:

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

La source


4
Juste une petite amélioration possible. Remplacez IF et le retour suivant par: return property? .GetValue (source);
Tomino

11

À propos de la discussion sur les propriétés imbriquées, vous pouvez éviter tous les éléments de réflexion si vous utilisez la DataBinder.Eval Method (Object, String)méthode ci-dessous:

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

Bien sûr, vous devrez ajouter une référence à l' System.Webassembly, mais ce n'est probablement pas un gros problème.


8

La méthode à appeler a changé dans .NET Standard (à partir de 1.6). Nous pouvons également utiliser l'opérateur conditionnel nul de C # 6.

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

1
pour utiliser le? operator
blfuentes

4

Utilisation de PropertyInfo de l' espace de noms System.Reflection . La réflexion se compile très bien, quelle que soit la propriété à laquelle nous essayons d'accéder. L'erreur se produira pendant l'exécution.

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

Cela fonctionne très bien pour obtenir la propriété Location d'un objet

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

Nous obtiendrons l'emplacement: {X = 71, Y = 27} Nous pouvons également retourner location.X ou location.Y de la même manière.


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

C'est un moyen d'obtenir toutes les propriétés avec leurs valeurs dans une liste.


Pourquoi faites-vous cela: type.GetProperty(pi.Name)quand c'est == à la variable pi?
weston

Si vous utilisez c # 6.0, débarrassez-vous ifet faites selfValue?.ToString()autrement. Débarrassez-vous ifet utilisezselfValue==null?null:selfValue.ToString()
weston

Également une liste List<KeyValuePair<est étrange, utilisez un dictionnaireDictionary<string, string>
weston

3

Le code suivant est une méthode récursive pour afficher la hiérarchie entière de tous les noms et valeurs de propriété contenus dans l'instance d'un objet. Cette méthode utilise une version simplifiée d'AlexDGetPropertyValue() réponse ci-dessus dans ce fil. Grâce à ce fil de discussion, j'ai pu comprendre comment faire ça!

Par exemple, j'utilise cette méthode pour afficher une explosion ou un vidage de toutes les propriétés dans une WebServiceréponse en appelant la méthode comme suit:

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

La méthode ci-dessous fonctionne parfaitement pour moi:

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

Pour obtenir la valeur de la propriété:

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

Pour définir la valeur de la propriété:

t1["prop1"] = value;

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

2

Voici une autre façon de trouver une propriété imbriquée qui ne nécessite pas la chaîne pour vous indiquer le chemin d'imbrication. Nous remercions Ed S. pour la méthode de la propriété unique.

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

Il pourrait être préférable de vérifier si les Type.GetPropertyretours null au lieu d'appeler GetValueet d'avoir NullReferenceExceptionjeté dans une boucle.
Groo

2

Vous ne mentionnez jamais quel objet vous inspectez, et puisque vous rejetez ceux qui font référence à un objet donné, je suppose que vous voulez dire un objet statique.

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

Notez que j'ai marqué l'objet qui est inspecté avec la variable locale obj. nullsignifie statique, sinon réglez-le sur ce que vous voulez. Notez également que c'est l' GetEntryAssembly()une des quelques méthodes disponibles pour obtenir l'assembly "en cours d'exécution", vous voudrez peut-être jouer avec si vous avez du mal à charger le type.


2

Jetez un œil à la bibliothèque Heleonix.Reflection . Vous pouvez obtenir / définir / invoquer des membres par des chemins, ou créer un getter / setter (lambda compilé en délégué) qui est plus rapide que la réflexion. Par exemple:

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

Ou créez un getter une fois et cachez-le pour le réutiliser (ceci est plus performant mais peut lever NullReferenceException si un membre intermédiaire est nul):

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

Ou si vous souhaitez créer un List<Action<object, object>>des différents getters, spécifiez simplement les types de base pour les délégués compilés (les conversions de type seront ajoutées dans les lambdas compilés):

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

1
n'utilisez jamais de bibliothèques tierces, si vous pouvez l'implémenter dans votre propre code dans un délai raisonnable en 5-10 lignes.
Artem G

1

manière plus courte ....

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 et AlexD ont tous deux écrit d'excellentes réponses sur la façon de résoudre les chaînes de propriétés. Je voudrais jeter la mienne dans le mix, car j'ai écrit une bibliothèque dédiée exactement à cet effet.

La classe principale de Pather.CSharp estResolver. Par défaut, il peut résoudre les entrées de propriétés, de tableau et de dictionnaire.

Ainsi, par exemple, si vous avez un objet comme celui-ci

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

et que vous voulez obtenir Property2, vous pouvez le faire comme ceci:

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

Il s'agit de l'exemple le plus simple des chemins qu'il peut résoudre. Si vous voulez voir ce qu'il peut d'autre ou comment l'étendre, rendez-vous simplement sur sa page Github .


0

Voici ma solution. Il fonctionne également avec les objets COM et permet d'accéder aux éléments de collection / tableau à partir d'objets 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

Voici ce que j'ai obtenu sur la base d'autres réponses. Un peu exagéré pour devenir si spécifique avec la gestion des erreurs.

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);
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.