Est-il possible de définir la propriété privée par réflexion?

125

Puis-je définir une propriété privée par réflexion?

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

J'ai essayé ce qui suit et cela ne fonctionne pas, où treprésente un type de Entity:

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

Je suppose que je peux le faire mais je ne peux pas y arriver.

AwkwardCoder
la source
2
Je sais que c'est tard, mais j'ai trouvé un besoin pour cette pensée que je partagerais mon «pourquoi». J'avais besoin de surmonter un inconvénient de certains logiciels tiers. Plus précisément, j'utilisais la méthode Crystal Reports ExportToStream. La manière dont cette méthode a été écrite, l'accès au tampon interne du flux n'était pas autorisé. Pour envoyer le rapport au navigateur, j'ai dû copier le flux dans un nouveau tampon (100K +), puis l'envoyer. En définissant le champ privé '_exposable' dans l'objet de flux sur 'true', j'ai pu envoyer le tampon interne directement, économisant une allocation de 100K + à chaque demande.
Ray
20
Pourquoi? Supposons que vous ayez des setters privés sur vos propriétés Id sur tous vos objets de domaine et que vous souhaitiez implémenter des tests de référentiel. Ensuite, ce n'est que dans votre projet de test de référentiel que vous voudrez pouvoir définir la propriété Id.
bounav
2
Autre scénario d'utilisation: définir des champs auto-générés comme "date de création" lors de l'importation de données.
ANeves
Une autre raison est que je suis simplement curieux de savoir si c'est possible. C'est ainsi que j'ai fini par voir cette question.
Caleb Mauer le

Réponses:

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

EDIT: Puisque la propriété elle-même est publique, vous n'avez apparemment pas besoin de l'utiliser BindingFlags.NonPublicpour la trouver. Appeler SetValuemalgré le fait que le setter ait moins d'accessibilité fait toujours ce que vous attendez.

Tinister
la source
5
Pour être juste, cela dépend du niveau de confiance, mais la réponse semble valable.
Marc Gravell
4
Méthode de jeu de propriétés introuvable dans System.Reflection.RuntimePropertyInfo.SetValue (Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object [] index, CultureInfo culture)
CZahrobsky
1
Cela fonctionne bien pour moi si je n'utilise pas de propriété virtuelle. Si je SetValue avec une propriété virtuelle, cela ne semble pas fonctionner.
JonathanPeel
105

Oui, ça l'est:

/// <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);
}
Arthur
la source
7
Juste pour protéger les cheveux de quelqu'un d'autre (qui vient d'être arraché sur ma tête): cela ne fonctionnera pas dans les environnements d'
Marc Wittke
SetValue serait meilleur que InvokeMember, puisque l'ancien supporte l'indice de passage
Chris Xue
8

Vous pouvez accéder au setter privé à partir du type dérivé via le code

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
la source
+1, mais juste une note ici. BaseType doit avoir toutes les propriétés que vous attendez. Si vous cachez une propriété (sans vous souvenir que vous l'avez fait), cela pourrait entraîner l'arrachement de certains cheveux.
ouflak
3

Aucun de ces éléments n'a fonctionné pour moi, et le nom de ma propriété était unique, alors j'ai simplement utilisé ceci:

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
la source
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
la source