Uzyskaj wartość właściwości z ciągu za pomocą odbicia w C #

928

Próbuję zaimplementować transformację danych przy użyciu przykładu Reflection 1 w moim kodzie.

GetSourceValueFunkcja posiada przełącznik porównujące różne typy, ale chcę, aby usunąć te rodzaje i właściwości i mają GetSourceValueuzyskać wartość właściwości przy użyciu tylko jednego ciągu jako parametru. Chcę przekazać klasę i właściwość w ciągu i rozstrzygnąć wartość właściwości.

czy to możliwe?

1 wersja archiwum internetowego oryginalnego posta na blogu

pedrofernandes
źródło

Odpowiedzi:

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

Oczywiście będziesz chciał dodać sprawdzanie poprawności i tak dalej, ale to jest sedno.

Ed S.
źródło
8
Ładnie i prosto! Uczyniłbym to jednak ogólnym:public static T GetPropertyValue<T>(object obj, string propName) { return (T)obj.GetType().GetProperty(propName).GetValue(obj, null); }
Ohad Schneider
2
Optymalizacja może wyeliminować ryzyko zerowego wyjątku, takiego jak: „ src.GetType().GetProperty(propName)?.GetValue(src, null);”;).
sh.t
8
@ shA.t: Myślę, że to zły pomysł. Jak rozróżnić wartość zerową istniejącej nieruchomości lub jej brak? Wolałbym od razu wiedzieć, że wysyłałem złą nazwę nieruchomości. To nie jest kod produkcyjny, ale lepszym ulepszeniem byłoby rzucenie bardziej szczegółowego wyjątku (np. Sprawdzenie, czy null jest włączony, GetPropertyczy rzucenie PropertyNotFoundExceptionlub coś, jeśli null.)
Ed S.
210

Co powiesz na coś takiego:

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

Umożliwi to zejście do właściwości za pomocą jednego ciągu, takiego jak ten:

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

Możesz użyć tych metod jako metod statycznych lub rozszerzeń.

jheddings
źródło
3
@FredJand cieszę się, że się na to natknąłeś! To zawsze zaskakujące, gdy pojawiają się te stare posty. To było trochę niejasne, więc dodałem trochę tekstu, aby to wyjaśnić. Przerzuciłem się również na używanie ich jako metod rozszerzenia i dodałem ogólny formularz, więc dodałem go tutaj.
jheddings,
Dlaczego wartownik zerowy znajduje się w foreach, a nie w górze?
Santhos
4
@ Santos, ponieważ „obj” jest redefiniowane w treści pętli foreach, jest sprawdzane podczas każdej iteracji.
jheddings,
Przydatne, ale w przypadku, gdy jedna z zagnieżdżonych właściwości może być ukryta (przy użyciu „nowego” modyfikatora), wygeneruje wyjątek dotyczący znajdowania duplikatów właściwości. Byłoby fajniej śledzić ostatni typ właściwości i używać PropertyInfo.PropertyTypezamiast obj.GetType()właściwości zagnieżdżonych, podobnie jak dostęp do właściwości w zagnieżdżonej właściwości.
Nullius
Możesz użyć nameofwyrażenia od C # 6 w ten sposób: nameof(TimeOfDay.Minutes)w parametrze name podczas wywoływania funkcji, aby pozbyć się magicznych ciągów i zwiększyć bezpieczeństwo tych połączeń w czasie kompilacji.
Reap
74

Dodaj do dowolnego 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; }
}

Następnie możesz użyć jako:

Foo f = new Foo();
// Set
f["Bar"] = "asdf";
// Get
string s = (string)f["Bar"];
Eduardo Cuomo
źródło
@EduardoCuomo: Czy można używać do tego refleksji, więc nie trzeba wiedzieć, którzy członkowie mają klasę?
Our Man in Bananas
Czy można to zrobić, jeśli „Bar” byłby przedmiotem?
big_water
@big_water the SetValuei GetValuemetody działają z Object. Jeśli chcesz pracować z określonym typem, powinieneś rzucić wynik GetValuei rzucić wartość, aby przypisać go za pomocąSetValue
Eduardo Cuomo
Przepraszam @OurManinBananas, nie rozumiem twojego pytania. Co chcesz robić?
Eduardo Cuomo,
Jak nazywa się tego typu metoda ..?
Sahan Chinthaka
45

Co o użyciu CallByNamew Microsoft.VisualBasicprzestrzeni nazw ( Microsoft.VisualBasic.dll)? Wykorzystuje odbicie, aby uzyskać właściwości, pola i metody normalnych obiektów, obiektów COM, a nawet obiektów dynamicznych.

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

i wtedy

Versioned.CallByName(this, "method/function/prop name", CallType.Get).ToString();
Fredou
źródło
5
Interesująca sugestia, dalsza kontrola wykazała, że ​​może obsłużyć zarówno pola, jak i właściwości, obiekty COM, a nawet poprawnie poprawnie powiązać dynamicznie !
IllidanS4 chce, aby Monica wróciła
Pojawia się błąd: nie znaleziono elementu publicznego „MyPropertyName” dla typu „MyType”.
vldmrrdjcc
30

Świetna odpowiedź od Jheddings. Chciałbym go ulepszyć, umożliwiając odwoływanie się do zagregowanych tablic lub kolekcji obiektów, aby właściwośćNazwa mogła być właściwością1.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;
    }
AlexD
źródło
co z listą list dostępną przez MasterList [0] [1]?
Jesse Adam
as Array -> as object [] również powoduje wyjątek Nullreference. To, co działa dla mnie (prop nie jest najbardziej wydajną metodą), to rzutowanie unknownCollection na IEnumerable, a następnie użycie ToArray () na wyniku. skrzypce
Jeroen Jonkman
14

Jeśli użyję kodu z Ed S. , dostanę

„ReflectionExtensions.GetProperty (Type, string)” jest niedostępny ze względu na poziom ochrony

Wygląda na GetProperty()to, że nie jest dostępny w Xamarin.Forms. TargetFrameworkProfilejest Profile7w mojej Portable Class Library (.NET Framework 4.5, Windows 8, ASP.NET Core 1.0, Xamarin.Android, Xamarin.iOS, Xamarin.iOS Classic).

Teraz znalazłem działające rozwiązanie:

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

Źródło

testowanie
źródło
4
Tylko niewielka możliwa poprawa. Zamień JEŻELI, a następnie zwróć przez: return property? .GetValue (source);
Tomino
11

Informacje o dyskusji na temat właściwości zagnieżdżonych można uniknąć, stosując DataBinder.Eval Method (Object, String)następujące elementy:

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

Oczywiście musisz dodać odniesienie do System.Webzestawu, ale to chyba nie jest wielka sprawa.

Rubens Farias
źródło
8

Metoda wywoływania zmieniła się w .NET Standard (od wersji 1.6). Możemy również użyć operatora warunkowego null C # 6.

using System.Reflection; 
public static object GetPropValue(object src, string propName)
{
    return src.GetType().GetRuntimeProperty(propName)?.GetValue(src);
}
Matt Frear
źródło
1
za korzystanie z? operator
blfuentes
4

Korzystanie z PropertyInfo przestrzeni nazw System.Reflection . Reflection kompiluje się dobrze bez względu na to, do jakiej własności próbujemy uzyskać dostęp. Błąd pojawi się w czasie wykonywania.

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

Działa dobrze, aby uzyskać właściwość Location obiektu

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

Otrzymamy lokalizację: {X = 71, Y = 27} Możemy również zwrócić lokalizację X lub lokalizację Y w ten sam sposób.

Ghazal
źródło
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;
    }

Jest to sposób na uzyskanie wszystkich właściwości z ich wartościami na liście.

Boncho Valkov
źródło
Dlaczego to robisz: type.GetProperty(pi.Name)kiedy to jest == do zmiennej pi?
weston
Jeśli używasz c # 6.0, pozbądź się ifi zrób selfValue?.ToString()inaczej. Pozbądź się ifi użyjselfValue==null?null:selfValue.ToString()
weston
Również lista List<KeyValuePair<jest nieparzysta, użyj słownikaDictionary<string, string>
weston
3

Poniższy kod jest metodą rekurencyjną do wyświetlania całej hierarchii wszystkich nazw i wartości właściwości zawartych w instancji obiektu. Ta metoda wykorzystuje uproszczoną wersję odpowiedzi AlexD GetPropertyValue()powyżej w tym wątku. Dzięki temu wątkowi dyskusyjnemu udało mi się dowiedzieć, jak to zrobić!

Na przykład używam tej metody, aby wyświetlić eksplozję lub zrzut wszystkich właściwości w WebServiceodpowiedzi, wywołując metodę w następujący sposób:

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);
    }
  }
}
gridtrak
źródło
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);
}
Rahma Samaroon
źródło
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"];
Komal Narang
źródło
3

Poniższa metoda działa dla mnie idealnie:

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

Aby uzyskać wartość nieruchomości:

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

Aby ustawić wartość właściwości:

t1["prop1"] = value;
Derrick.X
źródło
2
Dim NewHandle As YourType = CType(Microsoft.VisualBasic.CallByName(ObjectThatContainsYourVariable, "YourVariableName", CallType), YourType)
Kyle
źródło
2

Oto inny sposób na znalezienie zagnieżdżonej właściwości, która nie wymaga ciągu informującego o ścieżce zagnieżdżenia. Podziękowania dla Eda S. za metodę pojedynczej właściwości.

    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);
    }
Rekursor
źródło
Lepiej sprawdzić, czy Type.GetPropertyzwraca null zamiast dzwonić GetValuei NullReferenceExceptions rzucać w pętli.
Groo
2

Nigdy nie wspominasz o tym, jaki obiekt oglądasz, a ponieważ odrzucasz te, które odnoszą się do danego obiektu, zakładam, że masz na myśli obiekt statyczny.

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

Zauważ, że zaznaczyłem kontrolowany obiekt zmienną lokalną obj. nulloznacza statyczne, w przeciwnym razie ustaw to, co chcesz. Zauważ też, że GetEntryAssembly()jest to jedna z niewielu dostępnych metod uzyskania „działającego” zestawu, możesz się z nim pobawić, jeśli masz problemy z ładowaniem tego typu.

Guvante
źródło
2

Zajrzyj do biblioteki Heleonix.Reflection . Możesz pobrać / ustawić / wywołać członków ścieżkami lub utworzyć getter / setter (lambda skompilowana w delegata), który jest szybszy niż odbicie. Na przykład:

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

Lub stwórz raz getter i buforuj do ponownego użycia (jest to bardziej wydajne, ale może zgłosić wyjątek NullReferenceException, jeśli element pośredni ma wartość NULL):

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

Lub jeśli chcesz utworzyć List<Action<object, object>>inny program pobierający, po prostu określ typy podstawowe dla skompilowanych delegatów (konwersje typów zostaną dodane do skompilowanych lambdas):

var getter = Reflector.CreateGetter<object, object>("Date.Year", typeof(DateTime));
getter(DateTime.Now);
Hennadii Lutsyshyn
źródło
1
nigdy nie używaj bibliotek stron trzecich, jeśli możesz je zaimplementować we własnym kodzie w rozsądnym czasie w 5-10 liniach.
Artem G,
1

krótsza droga ....

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());
Budiantowang
źródło
1

jheddings i AlexD napisali doskonałe odpowiedzi na temat rozwiązywania ciągów właściwości. Chciałbym wrzucić moją do miksu, ponieważ napisałem dedykowaną bibliotekę właśnie w tym celu.

Główną klasą Pather.CSharp jestResolver. Domyślnie może rozpoznawać właściwości, tablicę i wpisy słownika.

Na przykład, jeśli masz taki obiekt

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

i chcesz to zrobić Property2, możesz to zrobić w następujący sposób:

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

Jest to najbardziej podstawowy przykład ścieżek, które może rozwiązać. Jeśli chcesz zobaczyć, co jeszcze może, lub jak to rozszerzyć, po prostu przejdź na stronę Github .

Domysee
źródło
0

Oto moje rozwiązanie. Działa również z obiektami COM i umożliwia dostęp do elementów kolekcji / tablicy z obiektów 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;
}
użytkownik3175253
źródło
0

Oto, co otrzymałem na podstawie innych odpowiedzi. Trochę przesada w uzyskiwaniu tak szczegółowych informacji przy obsłudze błędów.

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);
}
Jeff Codes
źródło