web-dev-qa-db-fra.com

Utilisation de la réflexion en C # pour obtenir les propriétés d'un objet imbriqué

Étant donné les objets suivants:

public class Customer {
    public String Name { get; set; }
    public String Address { get; set; }
}

public class Invoice {
    public String ID { get; set; }
    public DateTime Date { get; set; }
    public Customer BillTo { get; set; }
}

Je voudrais utiliser la réflexion pour parcourir la Invoice pour obtenir la propriété Name d'une Customer. Voici ce que je recherche, en supposant que ce code fonctionnerait:

Invoice inv = GetDesiredInvoice();  // magic method to get an invoice
PropertyInfo info = inv.GetType().GetProperty("BillTo.Address");
Object val = info.GetValue(inv, null);

Bien sûr, cela échoue car "BillTo.Address" n'est pas une propriété valide de la classe Invoice.

J'ai donc essayé d'écrire une méthode pour diviser la chaîne en morceaux sur la période et parcourir les objets à la recherche de la valeur finale qui m'intéressait. Cela fonctionne bien, mais je ne suis pas entièrement à l'aise avec cela:

public Object GetPropValue(String name, Object obj) {
    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;
}

Avez-vous des idées sur la façon d'améliorer cette méthode ou sur une meilleure façon de résoudre ce problème?

MODIFIER après la publication, j'ai vu quelques messages connexes ... Il ne semble pas y avoir de réponse qui traite spécifiquement de cette question, cependant. De plus, j'aimerais toujours avoir des commentaires sur ma mise en œuvre.

65
jheddings

En fait, je pense que votre logique est bonne. Personnellement, je le changerais probablement pour que vous passiez l'objet comme premier paramètre (qui est plus en ligne avec PropertyInfo.GetValue, donc moins surprenant).

Je l'appellerais probablement aussi quelque chose de plus comme GetNestedPropertyValue, pour qu'il soit évident qu'il recherche dans la pile des propriétés.

11
Reed Copsey

J'utilise cette méthode pour obtenir les valeurs de propriétés comme

"Propriété"

"Address.Street"

"Address.Country.Name"

    public static object GetPropertyValue(object src, string propName)
    {
        if (src == null) throw new ArgumentException("Value cannot be null.", "src");
        if (propName == null) throw new ArgumentException("Value cannot be null.", "propName");

        if(propName.Contains("."))//complex type nested
        {
            var temp = propName.Split(new char[] { '.' }, 2);
            return GetPropertyValue(GetPropertyValue(src, temp[0]), temp[1]);
        }
        else
        {
            var prop = src.GetType().GetProperty(propName);
            return prop != null ? prop.GetValue(src, null) : null;
        }
    }

Voici le violon: https://dotnetfiddle.net/PvKRH

11
DevT

Je sais que je suis un peu en retard à la fête, et comme d'autres l'ont dit, votre mise en œuvre est très bien
... pour les cas d'utilisation simples .
Cependant, j'ai développé une bibliothèque qui résout exactement ce cas d'utilisation, Pather.CSharp .
Il est également disponible sous la forme Nuget Package .

Sa classe principale est Resolver avec sa méthode Resolve.
Vous lui passez un objet et le chemin de la propriété , et il renverra la valeur souhaitée .

Invoice inv = GetDesiredInvoice();  // magic method to get an invoice
var resolver = new Resolver();
object result = resolver.Resolve(inv, "BillTo.Address");

Mais il peut également résoudre plus les chemins de propriété complexes , y compris l'accès aux tableaux et aux dictionnaires.
Ainsi, par exemple, si votre Customer avait plusieurs adresses

public class Customer {
    public String Name { get; set; }
    public IEnumerable<String> Addresses { get; set; }
}

vous pouvez accéder au second en utilisant Addresses[1].

Invoice inv = GetDesiredInvoice();  // magic method to get an invoice
var resolver = new Resolver();
object result = resolver.Resolve(inv, "BillTo.Addresses[1]");
10
Domysee

Vous devez accéder à l'objet ACTUEL sur lequel vous devez utiliser la réflexion. Voici ce que je veux dire:

Au lieu de cela:

Invoice inv = GetDesiredInvoice();  // magic method to get an invoice
PropertyInfo info = inv.GetType().GetProperty("BillTo.Address");
Object val = info.GetValue(inv, null);

Faites ceci (édité sur la base du commentaire):

Invoice inv = GetDesiredInvoice();  // magic method to get an invoice
PropertyInfo info = inv.GetType().GetProperty("BillTo");
Customer cust = (Customer)info.GetValue(inv, null);

PropertyInfo info2 = cust.GetType().GetProperty("Address");
Object val = info2.GetValue(cust, null);

Regardez cet article pour plus d'informations: tilisation de la réflexion pour définir une propriété d'une propriété d'un objet

10
Gabriel McAdams

Dans l'espoir de ne pas sonner trop tard pour la fête, je voudrais ajouter ma solution: utiliser certainement la récursivité dans cette situation

public static Object GetPropValue(String name, object obj, Type type)
    {
        var parts = name.Split('.').ToList();
        var currentPart = parts[0];
        PropertyInfo info = type.GetProperty(currentPart);
        if (info == null) { return null; }
        if (name.IndexOf(".") > -1)
        {
            parts.Remove(currentPart);
            return GetPropValue(String.Join(".", parts), info.GetValue(obj, null), info.PropertyType);
        } else
        {
            return info.GetValue(obj, null).ToString();
        }
    }
7
roger l

Vous n'expliquez pas la source de votre "inconfort", mais votre code me semble fondamentalement sain.

La seule chose que je remettrais en question est la gestion des erreurs. Vous retournez null si le code essaie de traverser une référence null ou si le nom de la propriété n'existe pas. Cela cache des erreurs: il est difficile de savoir s'il a retourné null parce qu'il n'y a pas de client BillTo, ou parce que vous l'avez mal orthographié "BilTo.Address" ... ou parce qu'il y a un client BillTo, et son adresse est nulle! Je laisserais la méthode planter et graver dans ces cas - laissez simplement l'exception s'échapper (ou peut-être la mettre dans une version plus conviviale).

6
itowlson
> Get Nest properties e.g., Developer.Project.Name
private static System.Reflection.PropertyInfo GetProperty(object t, string PropertName)
            {
                if (t.GetType().GetProperties().Count(p => p.Name == PropertName.Split('.')[0]) == 0)
                    throw new ArgumentNullException(string.Format("Property {0}, is not exists in object {1}", PropertName, t.ToString()));
                if (PropertName.Split('.').Length == 1)
                    return t.GetType().GetProperty(PropertName);
                else
                    return GetProperty(t.GetType().GetProperty(PropertName.Split('.')[0]).GetValue(t, null), PropertName.Split('.')[1]);
            }
2
Mohamed.Abdo
    public static string GetObjectPropertyValue(object obj, string propertyName)
    {
        bool propertyHasDot = propertyName.IndexOf(".") > -1;
        string firstPartBeforeDot;
        string nextParts = "";

        if (!propertyHasDot)
            firstPartBeforeDot = propertyName.ToLower();
        else
        {
            firstPartBeforeDot = propertyName.Substring(0, propertyName.IndexOf(".")).ToLower();
            nextParts = propertyName.Substring(propertyName.IndexOf(".") + 1);
        }

        foreach (var property in obj.GetType().GetProperties())
            if (property.Name.ToLower() == firstPartBeforeDot)
                if (!propertyHasDot)
                    if (property.GetValue(obj, null) != null)
                        return property.GetValue(obj, null).ToString();
                    else
                        return DefaultValue(property.GetValue(obj, null), propertyName).ToString();
                else
                    return GetObjectPropertyValue(property.GetValue(obj, null), nextParts);
        throw new Exception("Property '" + propertyName.ToString() + "' not found in object '" + obj.ToString() + "'");
    }
1
BarbaBabak

Voici une autre implémentation qui va sauter une propriété imbriquée si c'est un énumérateur et continuer plus profondément. Les propriétés de type chaîne ne sont pas affectées par la vérification d'énumération.

public static class ReflectionMethods
{
    public static bool IsNonStringEnumerable(this PropertyInfo pi)
    {
        return pi != null && pi.PropertyType.IsNonStringEnumerable();
    }

    public static bool IsNonStringEnumerable(this object instance)
    {
        return instance != null && instance.GetType().IsNonStringEnumerable();
    }

    public static bool IsNonStringEnumerable(this Type type)
    {
        if (type == null || type == typeof(string))
            return false;
        return typeof(IEnumerable).IsAssignableFrom(type);
    }

    public static Object GetPropValue(String name, Object obj)
    {
        foreach (String part in name.Split('.'))
        {
            if (obj == null) { return null; }
            if (obj.IsNonStringEnumerable())
            {
                var toEnumerable = (IEnumerable)obj;
                var iterator = toEnumerable.GetEnumerator();
                if (!iterator.MoveNext())
                {
                    return null;
                }
                obj = iterator.Current;
            }
            Type type = obj.GetType();
            PropertyInfo info = type.GetProperty(part);
            if (info == null) { return null; }

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

sur la base de cette question et

Comment savoir si un PropertyInfo est une collection par Berryl

Je l'utilise dans un projet MVC pour commander dynamiquement mes données en passant simplement la propriété à trier par Exemple:

result = result.OrderBy((s) =>
                {
                    return ReflectionMethods.GetPropValue("BookingItems.EventId", s);
                }).ToList();

où BookingItems est une liste d'objets.

1
erevosgr
   if (info == null) { /* throw exception instead*/ } 

Je lèverais en fait une exception s'ils demandent une propriété qui n'existe pas. La façon dont vous le faites coder, si j'appelle GetPropValue et qu'il retourne null, je ne sais pas si cela signifie que la propriété n'existait pas, ou la propriété existait mais sa valeur était nulle.

1
AaronLS

Ma connexion Internet était en panne lorsque je devais résoudre le même problème, j'ai donc dû "réinventer la roue":

static object GetPropertyValue(Object fromObject, string propertyName)
{
    Type objectType = fromObject.GetType();
    PropertyInfo propInfo = objectType.GetProperty(propertyName);
    if (propInfo == null && propertyName.Contains('.'))
    {
        string firstProp = propertyName.Substring(0, propertyName.IndexOf('.'));
        propInfo = objectType.GetProperty(firstProp);
        if (propInfo == null)//property name is invalid
        {
            throw new ArgumentException(String.Format("Property {0} is not a valid property of {1}.", firstProp, fromObject.GetType().ToString()));
        }
        return GetPropertyValue(propInfo.GetValue(fromObject, null), propertyName.Substring(propertyName.IndexOf('.') + 1));
    }
    else
    {
        return propInfo.GetValue(fromObject, null);
    }
}

À peu près sûr, cela résout le problème pour toute chaîne que vous utilisez pour le nom de la propriété, quelle que soit l'étendue de l'imbrication, tant que tout est une propriété.

0
MalibuCusser