¿Es posible establecer la propiedad privada a través de la reflexión?

125

¿Puedo establecer una propiedad privada a través de la reflexión?

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

He intentado lo siguiente y no funciona, donde trepresenta un tipo de Entity:

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

Creo que puedo hacer esto, pero no puedo resolverlo.

AwkwardCoder
fuente
2
Sé que es tarde, pero encontré la necesidad de este pensamiento, compartiría mi 'por qué'. Necesitaba superar un inconveniente en algún software de terceros. Específicamente, estaba usando el método Crystal Reports ExportToStream. Por la forma en que se escribió este método, no se permitió el acceso al búfer interno de la secuencia. Para enviar el informe al navegador, tuve que copiar la transmisión en un nuevo búfer (100K +) y luego enviarlo. Al establecer el campo privado '_exposable' en el objeto de flujo en 'verdadero', pude enviar el búfer interno directamente, ahorrando una asignación de 100K + en cada solicitud.
Ray
20
¿Por qué? Supongamos que tiene configuradores privados en sus propiedades de Id en todos sus objetos de dominio y desea implementar pruebas de repositorio. Entonces, solo en su proyecto de prueba de repositorio querrá poder establecer la propiedad Id.
bounav
2
Otro escenario de uso: establecer campos generados automáticamente como "fecha de creación" al importar datos.
ANeves
Otro por qué es que tengo curiosidad si es posible. Así es como terminé viendo esta pregunta.
Caleb Mauer

Respuestas:

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

EDITAR: Dado que la propiedad en sí es pública, aparentemente no necesita usarla BindingFlags.NonPublicpara encontrarla. Llamar a SetValuepesar de que el setter tiene menos accesibilidad sigue haciendo lo que espera.

Tinister
fuente
55
Para ser justos, depende del nivel de confianza, pero la respuesta parece válida.
Marc Gravell
44
El método del conjunto de propiedades no se encuentra en System.Reflection.RuntimePropertyInfo.SetValue (Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object [] index, CultureInfo culture)
CZahrobsky
1
Esto funciona bien para mí si no estoy usando una propiedad virtual. Si configuro el valor con una propiedad virtual, esto no parece funcionar.
JonathanPeel
105

Sí lo es:

/// <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);
}
Arturo
fuente
77
Solo para proteger el cabello de otra persona (que me acaban de sacar de la cabeza): esto no funcionará en tiempos de ejecución de Silverlight: msdn.microsoft.com/de-de/library/xb5dd1f1%28v=vs.95%29.aspx
Marc Wittke
SetValue sería mejor que InvokeMember, ya que el primero admite el índice de aprobación
Chris Xue
8

Puede acceder al setter privado desde el tipo derivado a través del código

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);
}
Siarhei Kuchuk
fuente
+1, pero solo una nota aquí. BaseType debe tener todas las propiedades que espera. Si está ocultando una propiedad (sin recordar que lo había hecho), podría provocar la extracción de algunos pelos.
ouflak
3

Ninguno de estos funcionó para mí, y el nombre de mi propiedad era único, así que solo usé esto:

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;
        }
    }
}
CZahrobsky
fuente
0
    //mock class
    public class Person{
        public string Name{get; internal set;}
    }

    // works for all types, update private field through reflection
    public static T ReviveType<T>(T t, string propertyName, object newValue){
        // add a check here that the object t and propertyName string are not null
        PropertyInfo pi = t.GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
         pi.SetValue(t, newValue, null); 
        return t;
    }

    // check the required function
    void Main()
    {
        var p = new Person(){Name="John"};
        Console.WriteLine("Name: {0}",p.Name);

        //box the person to object, just to see that the method never care about what type you pass it
        object o = p;
        var updatedPerson = ReviveType<Object>(o, "Name", "Webber") as Person;

         //check if it updated person instance
        Console.WriteLine("Name: {0}",updatedPerson.Name);
    }



// Console Result: -------------------
Name: John
Name: Webber
BTE
fuente