Можно ли установить частную собственность через отражение?

Могу ли я установить частную собственность через отражение?

public abstract class Entity
{
    private int _id;
    private DateTime? _createdOn;
    public virtual T Id
    {
        get { return _id; }
        private set { ChangePropertyAndNotify(ref _id, value, x => Id); }
    }
    public virtual DateTime? CreatedOn
    {
        get { return _createdOn; }
        private set { ChangePropertyAndNotify(ref _createdOn, value, x => CreatedOn); }
    }
}

Я пробовал следующее, и это не работает, где t представляет тип Entity:

var t = typeof(Entity);
var mi = t.GetMethod("set_CreatedOn", BindingFlags.Instance | BindingFlags.NonPublic);

Я думаю, я могу это сделать, но я не могу это решить.


person AwkwardCoder    schedule 14.10.2009    source источник
comment
Я знаю, что это поздно, но я нашел потребность в этой мысли, и я поделюсь своим «почему». Мне нужно было преодолеть неудобство в каком-то стороннем софте. В частности, я использовал метод ExportToStream в Crystal Reports. В том виде, как этот метод был написан, доступ к внутреннему буферу потока был запрещен. Чтобы отправить отчет в браузер, мне пришлось скопировать поток в новый буфер (100K+), а затем отправить его. Установив для частного поля «_exposable» в объекте потока значение «true», я смог напрямую отправлять внутренний буфер, сохраняя выделение более 100 КБ для каждого запроса.   -  person Ray    schedule 07.12.2009
comment
Почему? Допустим, у вас есть частные сеттеры для ваших свойств Id для всех объектов вашего домена, и вы хотите реализовать тесты репозитория. Тогда только в тестовом проекте репозитория вы сможете установить свойство Id.   -  person bounav    schedule 17.06.2010
comment
Другой сценарий использования: установка автоматически сгенерированных полей, таких как дата создания, при импорте данных.   -  person ANeves thinks SE is evil    schedule 12.11.2014
comment
Другое дело, что мне просто интересно, возможно ли это. Вот как я закончил просмотр этого вопроса.   -  person Caleb Mauer    schedule 01.02.2020


Ответы (4)


t.GetProperty("CreatedOn")
    .SetValue(obj, new DateTime(2009, 10, 14), null);

РЕДАКТИРОВАТЬ: Поскольку само свойство является общедоступным, вам, по-видимому, не нужно использовать BindingFlags.NonPublic, чтобы найти его. Вызов SetValue, несмотря на то, что сеттер имеет меньшую доступность, все же делает то, что вы ожидаете.

person Tinister    schedule 14.10.2009
comment
Честно говоря, это зависит от уровня доверия, но ответ кажется верным. - person Marc Gravell; 12.12.2009
comment
Метод набора свойств не найден в System.Reflection.RuntimePropertyInfo.SetValue (объект obj, значение объекта, BindingFlags invokeAttr, связыватель Binder, индекс Object[], культура CultureInfo) - person CZahrobsky; 24.08.2016
comment
Это отлично работает для меня, если я не использую виртуальную собственность. Если я SetValue с виртуальным свойством, это не работает. - person JonathanPeel; 25.05.2017
comment
У меня есть сомнения здесь. Если класс является абстрактным типом. Как мы можем создать его объект и передать его в качестве первого параметра SetValue? - person Nikhil Banka; 22.07.2021

Да, это:

/// <summary>
/// Returns a _private_ Property Value from a given Object. Uses Reflection.
/// Throws a ArgumentOutOfRangeException if the Property is not found.
/// </summary>
/// <typeparam name="T">Type of the Property</typeparam>
/// <param name="obj">Object from where the Property Value is returned</param>
/// <param name="propName">Propertyname as string.</param>
/// <returns>PropertyValue</returns>
public static T GetPrivatePropertyValue<T>(this object obj, string propName)
{
    if (obj == null) throw new ArgumentNullException("obj");
    PropertyInfo pi = obj.GetType().GetProperty(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
    if (pi == null) throw new ArgumentOutOfRangeException("propName", string.Format("Property {0} was not found in Type {1}", propName, obj.GetType().FullName));
    return (T)pi.GetValue(obj, null);
}

/// <summary>
/// Returns a private Property Value from a given Object. Uses Reflection.
/// Throws a ArgumentOutOfRangeException if the Property is not found.
/// </summary>
/// <typeparam name="T">Type of the Property</typeparam>
/// <param name="obj">Object from where the Property Value is returned</param>
/// <param name="propName">Propertyname as string.</param>
/// <returns>PropertyValue</returns>
public static T GetPrivateFieldValue<T>(this object obj, string propName)
{
    if (obj == null) throw new ArgumentNullException("obj");
    Type t = obj.GetType();
    FieldInfo fi = null;
    while (fi == null && t != null)
    {
        fi = t.GetField(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
        t = t.BaseType;
    }
    if (fi == null) throw new ArgumentOutOfRangeException("propName", string.Format("Field {0} was not found in Type {1}", propName, obj.GetType().FullName));
    return (T)fi.GetValue(obj);
}

/// <summary>
/// Sets a _private_ Property Value from a given Object. Uses Reflection.
/// Throws a ArgumentOutOfRangeException if the Property is not found.
/// </summary>
/// <typeparam name="T">Type of the Property</typeparam>
/// <param name="obj">Object from where the Property Value is set</param>
/// <param name="propName">Propertyname as string.</param>
/// <param name="val">Value to set.</param>
/// <returns>PropertyValue</returns>
public static void SetPrivatePropertyValue<T>(this object obj, string propName, T val)
{
    Type t = obj.GetType();
    if (t.GetProperty(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) == null)
        throw new ArgumentOutOfRangeException("propName", string.Format("Property {0} was not found in Type {1}", propName, obj.GetType().FullName));
    t.InvokeMember(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Instance, null, obj, new object[] { val });
}

/// <summary>
/// Set a private Property Value on a given Object. Uses Reflection.
/// </summary>
/// <typeparam name="T">Type of the Property</typeparam>
/// <param name="obj">Object from where the Property Value is returned</param>
/// <param name="propName">Propertyname as string.</param>
/// <param name="val">the value to set</param>
/// <exception cref="ArgumentOutOfRangeException">if the Property is not found</exception>
public static void SetPrivateFieldValue<T>(this object obj, string propName, T val)
{
    if (obj == null) throw new ArgumentNullException("obj");
    Type t = obj.GetType();
    FieldInfo fi = null;
    while (fi == null && t != null)
    {
        fi = t.GetField(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
        t = t.BaseType;
    }
    if (fi == null) throw new ArgumentOutOfRangeException("propName", string.Format("Field {0} was not found in Type {1}", propName, obj.GetType().FullName));
    fi.SetValue(obj, val);
}
person Arthur    schedule 14.10.2009
comment
Просто чтобы защитить чужие волосы (которые только что вырвали у меня на голове): это не будет работать в средах выполнения Silverlight: msdn.microsoft.com/de-de/library/xb5dd1f1%28v=vs.95%29.aspx - person Marc Wittke; 20.04.2012
comment
SetValue будет лучше, чем InvokeMember, так как первый поддерживает передачу индекса - person Chris Xue; 14.06.2016

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

public static void SetProperty(object instance, string propertyName, object newValue)
{
    Type type = instance.GetType();

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

    prop.SetValue(instance, newValue, null);
}
person Siarhei Kuchuk    schedule 10.02.2011
comment
+1, Но здесь просто примечание. BaseType должен иметь все ожидаемые свойства. Если вы прячете собственность (не помня, что вы это сделали), это может привести к выдергиванию некоторых волос. - person ouflak; 23.09.2016

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

public static void SetPrivatePropertyValue<T>(T obj, string propertyName, object newValue)
{
    // add a check here that the object obj and propertyName string are not null
    foreach (FieldInfo fi in obj.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic))
    {
        if (fi.Name.ToLower().Contains(propertyName.ToLower()))
        {
            fi.SetValue(obj, newValue);
            break;
        }
    }
}
person CZahrobsky    schedule 24.08.2016