Ustaw właściwość obiektu za pomocą odbicia

323

Czy istnieje sposób w języku C #, w którym można użyć odbicia do ustawienia właściwości obiektu?

Dawny:

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

Chcę ustawić obj.Namez refleksją. Coś jak:

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

Czy jest na to sposób?

Melursus
źródło

Odpowiedzi:

391

Tak, możesz użyć Type.InvokeMember():

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

Spowoduje to zgłoszenie wyjątku, jeśli objnie ma właściwości o nazwie Namelub nie można jej ustawić.

Innym podejściem jest uzyskanie metadanych dla właściwości, a następnie ustawienie jej. Umożliwi to sprawdzenie istnienia właściwości i sprawdzenie, czy można ją ustawić:

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
źródło
73
Jeśli nie masz do czynienia ze wszystkimi ciągami znaków, możesz najpierw przekonwertować dane: var val = Convert.ChangeType(propValue, propInfo.PropertyType); źródło: devx.com/vb2themax/Tip/19599
LostNomad311
4
alternatywnie możesz użyćobj.GetType().GetProperty("Name")?.GetSetMethod()?.Invoke(...)
tecfield
1
nie można ustawić wartości dla CanWrite=Falsetypów, prawda?
T.Todua
287

Możesz także:

Type type = target.GetType();

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

prop.SetValue (target, propertyValue, null);

gdzie cel to obiekt, dla którego zostanie ustawiona jego właściwość.

El Cheicon
źródło
11
Zrobiłem dzisiaj to samo. Powyższe działa świetnie, oczywiście przed próbą użycia na rekwizytach należy wykonać zerowanie.
Antony Scott
3
@AntonyScott Myślę, że chciałbyś wiedzieć, czy powołujesz się na niewłaściwą właściwość, więc „niepowodzenie w ciszy” wydaje się złym kursem.
dżih.
3
@jih Rozumiem twój punkt widzenia, ale tak naprawdę zależy to od sytuacji.
Antony Scott
94

Zasadniczo odbicie, tj

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

lub istnieją biblioteki, które pomogą zarówno pod względem wygody, jak i wydajności; na przykład w FastMember :

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

(co ma tę zaletę, że nie trzeba z góry wiedzieć, czy jest to pole czy nieruchomość)

Marc Gravell
źródło
Wow, trochę się pogubiłem po fuzji, ale znalazłem twoją odpowiedź ponownie! Dziękuję, zasługujesz na „zaakceptuj”, ale ponieważ mój wątek został scalony :(
Jeszcze
@MarcGravell, patrzyłem na FastMember i jest to dość interesujące. Czy jest gdzieś dla nas wprowadzenie / samouczek dla zwykłych śmiertelników, aby korzystać z waszej wspaniałej biblioteki?
Sudhanshu Mishra,
Jak mogę uzyskać typ właściwości przez FastMember?
powiedział Roohullah Allem
@Jahan accessor => GetMembers => Członek => Typ
Marc Gravell
świetna odpowiedź! Zaletą programu oneliner jest to, że jest on znacznie szybszy do zrozumienia, ponieważ nie ma żadnych zmiennych o nazwach użytkownika, pomiędzy którymi może nie mieć żadnego sensu.
Bastiaan
27

Lub możesz owinąć jedną wkładkę Marca we własną klasę rozszerzenia:

public static class PropertyExtension{       

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

i nazwij to tak:

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

Dla dokładności dodajmy metodę uzyskania wartości właściwości:

public static object GetPropertyValue(this object obj, string propName)
{
        return obj.GetType().GetProperty(propName).GetValue (obj, null);
}
Erik K.
źródło
14

Tak, używając System.Reflection:

using System.Reflection;

...

    string prop = "name";
    PropertyInfo pi = myObject.GetType().GetProperty(prop);
    pi.SetValue(myObject, "Bob", null);
D Stanley
źródło
13

Użyj czegoś takiego:

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

lub

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
źródło
2
Część, w której dostajesz typ właściwości, a następnie ją rzutujesz, była dla mnie bardzo przydatna. To działa jak urok. Dziękuję
Marc
12

Możesz również uzyskać dostęp do pól w podobny sposób:

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

Dzięki refleksji wszystko może być otwartą książką :) W moim przykładzie wiążemy się z polem na poziomie instancji prywatnej.

JoshBerke
źródło
8

Możesz to wypróbować, jeśli chcesz przypisać właściwości obiektu do innego obiektu za pomocą nazw właściwości:

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;
            }
        }
    }
}
użytkownik3679106
źródło
2
Cześć i witamy w Stack Overflow. Sformatuj kod poprawnie, a nie tylko zrzuć go w swoim poście, pomoże to innym zrozumieć twoją odpowiedź.
ThreeFx,
0

Właśnie opublikowałem pakiet Nuget, który pozwala ustawić nie tylko właściwości pierwszego poziomu, ale także zagnieżdżone właściwości w danym obiekcie na dowolnej głębokości.

Oto paczka

Ustawia wartość właściwości obiektu na podstawie ścieżki od katalogu głównego.

Obiekt może być obiektem złożonym, a właściwość może być wielopoziomową właściwością zagnieżdżoną głęboko lub może być właściwością bezpośrednio pod katalogiem głównym. ObjectWriterznajdzie właściwość za pomocą parametru ścieżki właściwości i zaktualizuje jej wartość. Ścieżka właściwości to dołączone nazwy właściwości odwiedzanych od katalogu głównego do właściwości węzła końcowego, które chcemy ustawić, rozdzielone parametrem ciągu separatora.

Stosowanie:

Aby skonfigurować właściwości bezpośrednio pod katalogiem głównym obiektu:

To znaczy. LineItemklasa ma właściwość int o nazwieItemId

LineItem lineItem = new LineItem();

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

Aby skonfigurować zagnieżdżoną właściwość na wielu poziomach poniżej katalogu głównego obiektu:

To znaczy. Inviteklasa ma właściwość o nazwie State, która ma właściwość o nazwie Invite(typu Invite), która ma właściwość o nazwie Recipient, która ma właściwość o nazwie Id.

Aby uczynić rzeczy jeszcze bardziej złożonymi, Statewłaściwość nie jest typem referencyjnym, jest to struct.

Oto jak możesz ustawić właściwość Id (na wartość ciągu „outlook”) na dole drzewa obiektów w jednym wierszu.

Invite invite = new Invite();

ObjectWriter.Set(invite, "State_Invite_Recipient_Id", "outlook", delimiter: "_");
Dogu Arslan
źródło
0

W oparciu o sugestię MarcGravell zbudowałem następującą metodę statyczną. Metoda ogólnie przypisuje wszystkie pasujące właściwości obiektu źródłowego do obiektu docelowego za pomocą 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);
            }
        }
    }
Przekonywający
źródło