Установить свойство объекта с помощью отражения

Есть ли способ в C# 3.5, где я могу использовать отражение, чтобы установить свойство объекта?

Пример:

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

Я хочу установить obj.Name с отражением. Что-то вроде:

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

Есть ли способ сделать это?

10 ответов

Решение

Да, вы можете использовать Type.InvokeMember():

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

Это вызовет исключение, если obj не имеет свойства под названием Nameили это не может быть установлено.

Другой подход состоит в том, чтобы получить метаданные для свойства, а затем установить его. Это позволит вам проверить наличие свойства и убедиться, что оно может быть установлено:

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

Вы также можете сделать:

Type type = target.GetType();

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

prop.SetValue (target, propertyValue, null);

где target - это объект, для которого будет установлено его свойство.

Отражение, в основном, т.е.

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

или есть библиотеки, которые помогут как с точки зрения удобства, так и производительности; например с FastMember:

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

(что также имеет то преимущество, что не нужно заранее знать, является ли это поле по сравнению со свойством)

Или вы можете обернуть один вкладыш Марка в свой собственный класс расширения:

public static class PropertyExtension{       

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

и назовите это так:

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

Для правильной оценки давайте добавим метод для получения значения свойства:

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

Используйте что-то вроде этого:

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

или же

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

Да, используя System.Reflection:

using System.Reflection;

...

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

Вы также можете получить доступ к полям, используя аналогичную манеру:

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

С отражением все может быть открытой книгой:) В моем примере мы привязываемся к частному полю уровня экземпляра.

Вы можете попробовать это, когда хотите массово назначить свойства объекта из другого объекта, используя имена свойств:

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

Я только что опубликовал пакет Nuget, который позволяет настраивать не только свойства первого уровня, но и вложенные свойства в данном объекте любой глубины.

Вот посылка

Устанавливает значение свойства объекта по его пути от корня.

Объект может быть сложным объектом, а свойство может быть многоуровневым вложенным свойством или может быть свойством непосредственно под корнем. ObjectWriter найдет свойство, используя параметр свойства path, и обновит его значение. Путь свойства - это добавленные имена свойств, посещаемых от корня до свойства конечного узла, которое мы хотим установить, ограниченные строковым параметром delimiter.

Использование:

Для настройки свойств непосредственно под корнем объекта:

То есть. LineItem класс имеет свойство int, называемое ItemId

LineItem lineItem = new LineItem();

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

Для настройки вложенных свойств на нескольких уровнях ниже корня объекта:

То есть. Invite класс имеет свойство под названием State, который имеет свойство под названием Invite (типа Пригласить), свойство которого называется Recipient, который имеет свойство под названием Id,

Чтобы сделать вещи еще сложнее, State свойство не является ссылочным типом, это struct,

Вот как вы можете установить свойство Id (в строковое значение "outlook") внизу дерева объектов в одной строке.

Invite invite = new Invite();

ObjectWriter.Set(invite, "State_Invite_Recipient_Id", "outlook", delimiter: "_");

Я могу установить свойство класса, используя отражение, где мои свойства класса являются только строкой. Теперь я должен ввести один словарь в качестве члена этого класса, но не могу установить значение. Я использую следующий фрагмент кода для свойств строки.

object objval;
objval=System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(className);
objval.GetType().GetProperty(classpropertyname).SetValue(objval,Value);

Как установить значение словаря с помощью отражения

Основываясь на предложении MarcGravell, я построил следующий статический метод. Метод обычно назначает все подходящие свойства от исходного объекта к цели, используя 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);
            }
        }
    }
Другие вопросы по тегам