web-dev-qa-db-fra.com

Récupère la valeur de la propriété de string en utilisant la réflexion en C #

J'essaie de mettre en œuvre la transformation de données en utilisant Reflection1 exemple dans mon code.

La fonction GetSourceValue a un commutateur comparant différents types, mais je souhaite supprimer ces types et propriétés et permettre à GetSourceValue d'obtenir la valeur de la propriété en utilisant une seule chaîne en tant que 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?

1Version Web Archive de l'article de blog original

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

Bien sûr, vous voudrez ajouter une validation, etc., mais c’est l’essentiel.

1494
Ed S.

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 en tant que méthodes ou extensions statiques.

188
jheddings

Ajoutez à n'importe quelle 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"];
52
Eduardo Cuomo

Qu'en est-il de l'utilisation de CallByName de l'espace de noms Microsoft.VisualBasic (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();
41
Fredou

Grande réponse par jheddings. J'aimerais l'améliorer pour permettre le référencement de tableaux ou de collections d'objets agrégés, 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 Array[];
                    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;
    }
26
AlexD

Si j'utilise le code de Ed S. je reçois

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

Il semble que GetProperty() ne soit pas disponible dans Xamarin.Forms. TargetFrameworkProfile est Profile7 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

11
testing

À propos de la discussion sur les propriétés imbriquées, vous pouvez éviter tout problème de réflexion si vous utilisez le DataBinder.Eval Method (Object, String) comme ci-dessous:

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

Bien sûr, vous aurez besoin d'ajouter une référence à l'assembly System.Web, mais ce n'est probablement pas un gros problème.

9
Rubens Farias

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);
}
6
Matt Frear

Utilisation de PropertyInfo de l’espace System.Reflection namespace. La réflexion compile parfaitement 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 bien pour obtenir la propriété Location d'un objet

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

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

4
A Ghazal
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.

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"];
2
Komal Narang

Voici un autre moyen de rechercher une propriété imbriquée ne nécessitant pas de chaîne pour vous indiquer le chemin d'imbrication. Crédit à 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);
    }
2
Recursor
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);
}
2
Rahma Sammaron

Vous ne mentionnez jamais l'objet que vous inspectez, et comme vous rejetez ceux qui font référence à un objet donné, je suppose que vous voulez dire 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. null signifie statique, sinon définissez-le comme vous le souhaitez. Notez également que la fonction GetEntryAssembly() est l’une des méthodes disponibles pour obtenir l’assemblage "en cours". Vous voudrez peut-être jouer avec elle si vous avez du mal à charger le type.

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

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
Budiantowang

jheddings et AlexD ont tous deux d'excellentes réponses sur la façon de résoudre les chaînes de propriétés. J'aimerais ajouter le mien à la composition, car j'ai écrit une bibliothèque dédiée exactement à cette fin.

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

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

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

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

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

C'est l'exemple le plus simple des chemins qu'il peut résoudre. Si vous voulez voir quoi d’autre, ou comment vous pouvez l’agrandir, dirigez-vous simplement vers la page Github .

1
Domysee

Jetez un coup d’œ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é) 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 mettez-le en cache pour le réutiliser (ceci est plus performant mais peut générer une exception NullReferenceException si un membre intermédiaire est null):

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

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

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

Voici ce que j'ai obtenu à partir d'autres réponses. Un peu exagéré de 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);
}
0
Jeff Codes

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
user3175253

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;
0
Derrick.X