Definir propriedade do objeto usando reflexão

323

Existe uma maneira em c # onde eu possa usar a reflexão para definir uma propriedade de objeto?

Ex:

MyObject obj = new MyObject();
obj.Name = "Value";

Eu quero definir obj.Namecom reflexão. Algo como:

Reflection.SetProperty(obj, "Name") = "Value";

Existe alguma forma de fazer isso?

Melursus
fonte

Respostas:

391

Sim, você pode usar Type.InvokeMember():

using System.Reflection;
MyObject obj = new MyObject();
obj.GetType().InvokeMember("Name",
    BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty,
    Type.DefaultBinder, obj, "Value");

Isso gerará uma exceção se objnão tiver uma propriedade chamada Nameou se não puder ser definida.

Outra abordagem é obter os metadados da propriedade e configurá-los. Isso permitirá que você verifique a existência da propriedade e verifique se ela pode ser configurada:

using System.Reflection;
MyObject obj = new MyObject();
PropertyInfo prop = obj.GetType().GetProperty("Name", BindingFlags.Public | BindingFlags.Instance);
if(null != prop && prop.CanWrite)
{
    prop.SetValue(obj, "Value", null);
}
Andy
fonte
73
Se você não está lidando com todas as cordas que você pode querer converter os dados em primeiro lugar: var val = Convert.ChangeType(propValue, propInfo.PropertyType); fonte: devx.com/vb2themax/Tip/19599
LostNomad311
4
alternativamente, você pode usarobj.GetType().GetProperty("Name")?.GetSetMethod()?.Invoke(...)
tecfield
1
é impossível definir valor para os CanWrite=Falsetipos, certo?
T.Todua
287

Você também pode fazer:

Type type = target.GetType();

PropertyInfo prop = type.GetProperty("propertyName");

prop.SetValue (target, propertyValue, null);

onde target é o objeto que terá sua propriedade definida.

El Cheicon
fonte
11
Eu fiz exatamente a mesma coisa hoje. O exemplo acima funciona muito bem, obviamente uma verificação nula deve ser feita no suporte antes de tentar usá-lo.
6139 Antony Scott
3
@AntonyScott Eu acho que você gostaria de saber se está invocando a propriedade errada, então "falhar silenciosamente" parece um curso ruim.
jih
3
@jih eu posso entender seu ponto de vista, mas depende realmente da situação.
Antony Scott
94

Reflexão, basicamente, ie

myObject.GetType().GetProperty(property).SetValue(myObject, "Bob", null);

ou existem bibliotecas para ajudar em termos de conveniência e desempenho; por exemplo, com FastMember :

var wrapped = ObjectAccessor.Create(obj); 
wrapped[property] = "Bob";

(que também tem a vantagem de não precisar saber com antecedência se é um campo versus uma propriedade)

Marc Gravell
fonte
Uau, fiquei um pouco confuso com a mesclagem, mas encontrei sua resposta novamente! Obrigado, você merece uma 'aceitação', mas desde que meu tópico foi mesclado :( Obrigado novamente!
halfpastfour.am
@ MarcGravell, eu estava olhando para FastMember e é bem interessante. Existe algum tutorial para começar em algum lugar para nós, meros mortais, usar esta grande lib sua?
Sudhanshu Mishra
Como posso obter o tipo de propriedade pelo FastMember?
Disse Roohullah Allem
@Jahan accessor => GetMembers => Member => Type
Marc Gravell
Ótima resposta! A coisa boa sobre um oneliner é que é muito mais rápido para entender, já que não há variáveis de usuário chamado no meio que para si mesmo pode não fazem qualquer sentido ..
Bastiaan
27

Ou você pode envolver um liner de Marc dentro de sua própria classe de extensão:

public static class PropertyExtension{       

   public static void SetPropertyValue(this object obj, string propName, object value)
    {
        obj.GetType().GetProperty(propName).SetValue(obj, value, null);
    }
}

e chame assim:

myObject.SetPropertyValue("myProperty", "myValue");

Para uma boa medida, vamos adicionar um método para obter um valor de propriedade:

public static object GetPropertyValue(this object obj, string propName)
{
        return obj.GetType().GetProperty(propName).GetValue (obj, null);
}
Erik K.
fonte
14

Sim, usando System.Reflection:

using System.Reflection;

...

    string prop = "name";
    PropertyInfo pi = myObject.GetType().GetProperty(prop);
    pi.SetValue(myObject, "Bob", null);
D Stanley
fonte
13

Use algo assim:

public static class PropertyExtension{       

   public static void SetPropertyValue(this object p_object, string p_propertyName, object value)
   {
    PropertyInfo property = p_object.GetType().GetProperty(p_propertyName);
    property.SetValue(p_object, Convert.ChangeType(value, property.PropertyType), null);
   }
}

ou

public static class PropertyExtension{       

   public static void SetPropertyValue(this object p_object, string p_propertyName, object value)
   {
    PropertyInfo property = p_object.GetType().GetProperty(p_propertyName);
    Type t = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
    object safeValue = (value == null) ? null : Convert.ChangeType(value, t);

    property.SetValue(p_object, safeValue, null);
   }
}
Ardalan Shahgholi
fonte
2
A parte em que você obtém o tipo de propriedade e depois a transmite foi realmente útil para mim. Ele funciona como um encanto. Obrigado
Marc
12

Você também pode acessar os campos usando uma maneira semelhante:

var obj=new MyObject();
FieldInfo fi = obj.GetType().
  GetField("Name", BindingFlags.NonPublic | BindingFlags.Instance);
fi.SetValue(obj,value)

Com reflexão, tudo pode ser um livro aberto :) No meu exemplo, estamos vinculando a um campo no nível da instância privada.

JoshBerke
fonte
8

Você pode tentar isso quando quiser atribuir em massa propriedades de um Objeto de outro Objeto usando nomes de Propriedades:

public static void Assign(this object destination, object source)
    {
        if (destination is IEnumerable && source is IEnumerable)
        {
            var dest_enumerator = (destination as IEnumerable).GetEnumerator();
            var src_enumerator = (source as IEnumerable).GetEnumerator();
            while (dest_enumerator.MoveNext() && src_enumerator.MoveNext())
                dest_enumerator.Current.Assign(src_enumerator.Current);
        }
        else
        {
            var destProperties = destination.GetType().GetProperties();
            foreach (var sourceProperty in source.GetType().GetProperties())
            {
                foreach (var destProperty in destProperties)
                {
                    if (destProperty.Name == sourceProperty.Name && destProperty.PropertyType.IsAssignableFrom(sourceProperty.PropertyType))
                    {
                        destProperty.SetValue(destination,     sourceProperty.GetValue(source, new object[] { }), new object[] { });
                        break;
            }
        }
    }
}
user3679106
fonte
2
Olá e bem-vindo ao Stack Overflow. Formate seu código corretamente e não apenas despeje-o em sua postagem, pois ajudará outras pessoas a entender sua resposta.
ThreeFx
0

Acabei de publicar um pacote Nuget que permite configurar não apenas as propriedades de primeiro nível, mas também as propriedades aninhadas no objeto especificado em qualquer profundidade.

Aqui está o pacote

Define o valor de uma propriedade de um objeto pelo caminho a partir da raiz.

O objeto pode ser um objeto complexo e a propriedade pode ser uma propriedade aninhada profunda em vários níveis ou pode ser uma propriedade diretamente sob a raiz. ObjectWriterencontrará a propriedade usando o parâmetro path da propriedade e atualizará seu valor. Caminho da propriedade são os nomes anexados das propriedades visitadas da raiz até a propriedade do nó final que queremos definir, delimitadas pelo parâmetro delimiter string.

Uso:

Para configurar as propriedades diretamente sob a raiz do objeto:

Ou seja. LineItemclasse tem uma propriedade int chamadaItemId

LineItem lineItem = new LineItem();

ObjectWriter.Set(lineItem, "ItemId", 13, delimiter: null);

Para configurar a propriedade aninhada vários níveis abaixo da raiz do objeto:

Ou seja. InviteA classe possui uma propriedade chamada State, que possui uma propriedade chamada Invite(do tipo Invite), que possui uma propriedade chamada Recipient, que possui uma propriedade chamada Id.

Para tornar as coisas ainda mais complexas, a Statepropriedade não é um tipo de referência, é a struct.

Aqui está como você pode definir a propriedade Id (para o valor da string "outlook") na parte inferior da árvore de objetos em uma única linha.

Invite invite = new Invite();

ObjectWriter.Set(invite, "State_Invite_Recipient_Id", "outlook", delimiter: "_");
Dogu Arslan
fonte
0

Com base na sugestão de MarcGravell, construí o método estático a seguir. O método atribui genericamente todas as propriedades correspondentes do objeto de origem ao destino usando o FastMember

 public static void DynamicPropertySet(object source, object target)
    {
        //SOURCE
        var src_accessor = TypeAccessor.Create(source.GetType());
        if (src_accessor == null)
        {
            throw new ApplicationException("Could not create accessor!");
        }
        var src_members = src_accessor.GetMembers();
        if (src_members == null)
        {
            throw new ApplicationException("Could not fetch members!");
        }
        var src_class_members = src_members.Where(x => x.Type.IsClass && !x.Type.IsPrimitive);
        var src_class_propNames = src_class_members.Select(x => x.Name);
        var src_propNames = src_members.Except(src_class_members).Select(x => x.Name);

        //TARGET
        var trg_accessor = TypeAccessor.Create(target.GetType());
        if (trg_accessor == null)
        {
            throw new ApplicationException("Could not create accessor!");
        }
        var trg_members = trg_accessor.GetMembers();
        if (trg_members == null)
        {
            throw new ApplicationException("Could not create accessor!");
        }
        var trg_class_members = trg_members.Where(x => x.Type.IsClass && !x.Type.IsPrimitive);
        var trg_class_propNames = trg_class_members.Select(x => x.Name);
        var trg_propNames = trg_members.Except(trg_class_members).Select(x => x.Name);



        var class_propNames = trg_class_propNames.Intersect(src_class_propNames);
        var propNames = trg_propNames.Intersect(src_propNames);

        foreach (var propName in propNames)
        {
            trg_accessor[target, propName] = src_accessor[source, propName];
        }
        foreach (var member in class_propNames)
        {
            var src = src_accessor[source, member];
            var trg = trg_accessor[target, member];
            if (src != null && trg != null)
            {
                DynamicPropertySet(src, trg);
            }
        }
    }
Convincente
fonte