Obter valor da propriedade da string usando reflexão em C #


928

Estou tentando implementar a transformação de dados usando o exemplo de reflexão 1 no meu código.

A GetSourceValuefunção possui uma opção que compara vários tipos, mas quero remover esses tipos e propriedades e GetSourceValueobter o valor da propriedade usando apenas uma única string como parâmetro. Eu quero passar uma classe e propriedade na string e resolver o valor da propriedade.

Isso é possível?

1 versão do arquivo da web da postagem original do blog

Respostas:


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

Obviamente, você desejará adicionar validação e outros enfeites, mas essa é a essência.


8
Agradável e simples! Eu tinha que fazer isso genérico no entanto:public static T GetPropertyValue<T>(object obj, string propName) { return (T)obj.GetType().GetProperty(propName).GetValue(obj, null); }
Ohad Schneider

2
Uma otimização pode remover o risco de exceção nula como esta: " src.GetType().GetProperty(propName)?.GetValue(src, null);";).
shA.t 12/12/19

8
@ shA.t: Eu acho que é uma péssima ideia. Como você diferencia entre um valor nulo de uma propriedade existente ou nenhuma propriedade? Prefiro saber imediatamente que estava enviando um nome de propriedade inválido. Este não é um código de produção, mas uma melhoria melhor seria lançar uma exceção mais específica (por exemplo, verificar nulo GetPropertye lançar PropertyNotFoundExceptionou algo se nulo).
Ed S.

210

Que tal algo como isso:

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

Isso permitirá que você desça para as propriedades usando uma única string, assim:

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

Você pode usar esses métodos como métodos estáticos ou extensões.


3
@FredJand feliz que você tropeçou nele! É sempre surpreendente quando essas postagens antigas aparecem. Era um pouco vago, então adicionei um pouco de texto para explicá-lo. Também mudei para usá-los como métodos de extensão e adicionei um formulário genérico, então o adicionei aqui.
perfil completo de Jheddings

Por que a guarda nula está no foreach e não está acima?
Santhos 17/05

4
@Shos: como 'obj' é redefinido no corpo do loop foreach, ele é verificado a cada iteração.
Jheddings

Útil, mas no caso extremo que uma das propriedades aninhadas possa estar oculta (usando o modificador 'new'), será lançada uma exceção para a localização de propriedades duplicadas. Seria mais limpo acompanhar o último tipo de propriedade e usá-lo em PropertyInfo.PropertyTypevez de obj.GetType()em propriedades aninhadas, da mesma forma que acessaria a propriedade em uma propriedade aninhada.
Nullius 20/04

Você pode usar a nameofexpressão a partir do C # 6 assim: nameof(TimeOfDay.Minutes)no parâmetro name ao chamar a função para livrar as seqüências de caracteres mágicas e adicionar segurança de tempo de compilação a essas chamadas.
Colher

74

Adicione a qualquer 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; }
}

Em seguida, você pode usar como:

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

@EduardoCuomo: É possível usar a reflexão com isso para que você não precise saber quais membros da turma?
Our Man in Bananas

É possível fazer isso se "Bar" fosse um objeto?
big_water

@big_water os métodos SetValuee GetValuetrabalham com Object. Se você precisa trabalhar com um tipo específico, deve converter o resultado GetValuee atribuir o valor ao qual deseja atribuí-loSetValue
Eduardo Cuomo

Desculpe @OurManinBananas, não consigo entender sua pergunta. O que você quer fazer?
Eduardo Cuomo

Qual é o nome desse tipo de métodos ..?
Sahan Chinthaka

45

Que tal usar CallByNameo Microsoft.VisualBasicnamespace ( Microsoft.VisualBasic.dll)? Ele usa a reflexão para obter propriedades, campos e métodos de objetos normais, objetos COM e até objetos dinâmicos.

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

e depois

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

5
Sugestão interessante, uma inspeção mais aprofundada provou que ele pode lidar com campos e propriedades, objetos COM, e até com ligação dinâmica correta !
IllidanS4 quer Monica de volta 2/14

Estou recebendo um erro: Membro público 'MyPropertyName' no tipo 'MyType' não encontrado.
vldmrrdjcc 4/06/19

30

Ótima resposta por jheddings. Gostaria de aperfeiçoá-lo, permitindo a referência de matrizes agregadas ou coleções de objetos, para que propertyName possa ser 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;
    }

que tal uma lista de listas acessadas pela MasterList [0] [1]?
Jesse Adam

como Matriz -> como objeto [] também resulta em exceção Nullreference. O que funciona para mim (prop não é o método mais eficiente) é converter unknownCollection para IEnumerable e usar ToArray () no resultado. fiddle
Jeroen Jonkman

14

Se eu usar o código de Ed S. , recebo

'ReflectionExtensions.GetProperty (Type, string)' está inacessível devido ao seu nível de proteção

Parece que GetProperty()não está disponível no Xamarin.Forms. TargetFrameworkProfileéProfile7 na minha Biblioteca de Classes Portátil (.NET Framework 4.5, Windows 8, ASP.NET Core 1.0, Xamarin.Android, Xamarin.iOS, Xamarin.iOS Classic).

Agora eu encontrei uma solução de trabalho:

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
Apenas uma pequena melhoria possível. Substitua IF e próximo retorno por: return property? .GetValue (source);
Tomino

11

Sobre a discussão de propriedades aninhadas, você pode evitar todo o material de reflexão se usar o DataBinder.Eval Method (Object, String)seguinte:

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

Obviamente, você precisará adicionar uma referência à System.Webmontagem, mas isso provavelmente não é grande coisa.


8

O método para chamar foi alterado no .NET Standard (a partir da versão 1.6). Também podemos usar o operador condicional nulo do C # 6.

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

1
para usar o? operator
blfuentes

4

Usando PropertyInfo do espaço para nome System.Reflection . A reflexão compila muito bem, não importa a propriedade que tentamos acessar. O erro será exibido durante o tempo de execução.

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

Funciona bem para obter a propriedade Location de um objeto

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

Obteremos o local: {X = 71, Y = 27} Também podemos retornar location.X ou location.Y da mesma maneira.


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

Essa é uma maneira de obter todas as propriedades com seus valores em uma Lista.


Por que está fazendo isso: type.GetProperty(pi.Name)quando isso é == para a variável pi?
weston

Se você estiver usando C # 6.0, se livrar ife não selfValue?.ToString()Caso contrário, se livrar de ife usoselfValue==null?null:selfValue.ToString()
Weston

Também uma lista de List<KeyValuePair<é ímpar, use um dicionárioDictionary<string, string>
weston

3

O código a seguir é um método recursivo para exibir toda a hierarquia de todos os nomes e valores de propriedades contidos na instância de um objeto. Este método usa uma versão simplificada da GetPropertyValue()resposta de AlexD acima neste tópico. Graças a esta discussão, consegui descobrir como fazer isso!

Por exemplo, eu uso esse método para mostrar uma explosão ou despejo de todas as propriedades em uma WebServiceresposta chamando o método da seguinte maneira:

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

O método abaixo funciona perfeito para mim:

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

Para obter o valor da propriedade:

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

Para definir o valor da propriedade:

t1["prop1"] = value;

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

2

Aqui está outra maneira de encontrar uma propriedade aninhada que não exija que a cadeia indique o caminho de aninhamento. Crédito para Ed S. pelo método da propriedade única.

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

Talvez seja melhor verificar se Type.GetPropertyretorna em null vez de chamar GetValuee ter sido NullReferenceExceptionlançado em um loop.
Groo

2

Você nunca menciona qual objeto está inspecionando e, como está rejeitando aqueles que fazem referência a um determinado objeto, assumirei que você quer dizer um estático.

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

Observe que marquei o objeto que está sendo inspecionado com a variável local obj. nullsignifica estático; caso contrário, configure-o como desejar. Observe também que esse GetEntryAssembly()é um dos poucos métodos disponíveis para obter a montagem "em execução"; convém brincar com ela se estiver com dificuldades para carregar o tipo.


2

Dê uma olhada na biblioteca Heleonix.Reflection . Você pode obter / definir / chamar membros por caminhos ou criar um getter / setter (lambda compilado em um delegado) que seja mais rápido que a reflexão. Por exemplo:

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

Ou crie um getter uma vez e faça cache para reutilização (isso é mais eficiente, mas pode gerar NullReferenceException se um membro intermediário for nulo):

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

Ou, se você deseja criar List<Action<object, object>>diferentes getters, especifique os tipos de base para os delegados compilados (as conversões de tipo serão adicionadas às lambdas compiladas):

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

1
nunca use bibliotecas de terceiros, se você puder implementá-lo em seu próprio código em um prazo razoável em 5 a 10 linhas.
Artem G # /

1

caminho mais curto ....

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 escreveram excelentes respostas sobre como resolver sequências de propriedades. Eu gostaria de colocar a minha na mistura, já que escrevi uma biblioteca dedicada exatamente para esse fim.

A classe principal de Pather.CSharp éResolver. Por padrão, ele pode resolver propriedades, entradas de matriz e dicionário.

Então, por exemplo, se você tiver um objeto como este

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

e deseja obter Property2, você pode fazer assim:

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

Este é o exemplo mais básico dos caminhos que ele pode resolver. Se você quiser ver o que mais ele pode ou como pode estendê-lo, vá para a página do Github .


0

Aqui está a minha solução. Também funciona com objetos COM e permite acessar itens de coleção / matriz de objetos 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

Aqui está o que eu recebi com base em outras respostas. Um pouco de exagero ao ser tão específico com o tratamento de erros.

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);
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.