web-dev-qa-db-fra.com

Comment définir la valeur de la propriété d'un objet anonyme?

voici mon code par exemple:

var output = new
{
    NetSessionId = string.Empty
};

foreach (var property in output.GetType().GetProperties())
{
    property.SetValue(output, "Test", null);
}

Il se produit une exception: "Méthode de jeu de propriétés introuvable". Je veux savoir comment créer un type anonyme avec des propriétés pouvant être définies.

Merci.

21
Leo Vo

Les propriétés de type anonyme sont en lecture seule et ne peuvent pas être définies.

Les types anonymes fournissent un moyen pratique d'encapsuler un ensemble de propriétés en lecture seule dans un seul objet sans avoir à explicitement définir un type d'abord. Le nom du type est généré par le compilateur et est non disponible au niveau du code source. Le type de chaque propriété est déduit par le compilateur.

Types anonymes (Guide de programmation C #)

27
MarcinJuraszek

Comment définir la valeur de la propriété d'un objet anonyme?

Comme on me rappelait aujourd’hui que rien n’était vraiment immuable lorsque la réflexion était combinée à la connaissance de la façon dont certaines choses étaient implémentées (champs de sauvegarde pour les propriétés en lecture seule des types anonymes dans ce cas), j’ai jugé sage d’ajouter une réponse illustrant les valeurs de propriété d'un objet anonyme peuvent être modifiées en les mappant à leurs champs de support.

Cette méthode repose sur une convention spécifique utilisée par le compilateur pour nommer ces champs de sauvegarde: <xxxxx>i__Field en .NET et <xxxxx> sur Mono, avec la variable xxxxx représentant le nom de la propriété. Si cette convention devait changer, le code ci-dessous échouera (remarque: il échouera également si vous essayez de lui donner quelque chose de différent du type anonyme).

public static class AnonymousObjectMutator
{
    private const BindingFlags FieldFlags = BindingFlags.NonPublic | BindingFlags.Instance;
    private static readonly string[] BackingFieldFormats = { "<{0}>i__Field", "<{0}>" };

    public static T Set<T, TProperty>(
        this T instance,
        Expression<Func<T, TProperty>> propExpression,
        TProperty newValue) where T : class
    {
        var pi = (propExpression.Body as MemberExpression).Member;
        var backingFieldNames = BackingFieldFormats.Select(x => string.Format(x, pi.Name)).ToList();
        var fi = typeof(T)
            .GetFields(FieldFlags)
            .FirstOrDefault(f => backingFieldNames.Contains(f.Name));
        if (fi == null)
            throw new NotSupportedException(string.Format("Cannot find backing field for {0}", pi.Name));
        fi.SetValue(instance, newValue);
        return instance;
    }
}

Échantillon:

public static void Main(params string[] args)
{
    var myAnonInstance = new { 
        FirstField = "Hello", 
        AnotherField = 30, 
    };
    Console.WriteLine(myAnonInstance);

    myAnonInstance
        .Set(x => x.FirstField, "Hello SO")
        .Set(x => x.AnotherField, 42);
    Console.WriteLine(myAnonInstance);
}

Avec sortie:

{ FirstField = Hello, AnotherField = 30 }
{ FirstField = Hello SO, AnotherField = 42 }

On peut trouver une version légèrement plus élaborée ici

18
Alex

Si vous rencontrez un jour une situation dans laquelle vous avez besoin d'un type mutable, au lieu de jouer avec le type Anonymous, vous pouvez simplement utiliser ExpandoObject:

Exemple :

var people = new List<Person>
{
    new Person { FirstName = "John", LastName = "Doe" },
    new Person { FirstName = "Jane", LastName = "Doe" },
    new Person { FirstName = "Bob", LastName = "Saget" },
    new Person { FirstName = "William", LastName = "Drag" },
    new Person { FirstName = "Richard", LastName = "Johnson" },
    new Person { FirstName = "Robert", LastName = "Frost" }
};

// Method syntax.
var query = people.Select(p =>
{
    dynamic exp = new ExpandoObject();
    exp.FirstName = p.FirstName;
    exp.LastName = p.LastName;
    return exp;
}); // or people.Select(p => GetExpandoObject(p))

// Query syntax.
var query2 = from p in people
             select GetExpandoObject(p);

foreach (dynamic person in query2) // query2 or query
{
    person.FirstName = "Changed";
    Console.WriteLine("{0} {1}", person.FirstName, person.LastName);
}

// Used with the query syntax in this example, but may also be used 
// with the method syntax just as easily.
private ExpandoObject GetExpandoObject(Person p)
{
    dynamic exp = new ExpandoObject();
    exp.FirstName = p.FirstName;
    exp.LastName = p.LastName;
    return exp;
}
5
B.K.

Les types anonymes sont immuables en C #. Je ne pense pas que vous puissiez changer la propriété là-bas.

1
Stay Foolish

Une suggestion: vous pouvez définir toutes les propriétés à la fois.

Les autres réponses suggèrent correctement qu’il s’agit d’objets immuables (bien que la réponse d’Alex montre comment accéder aux champs de fond, ce qui est une bonne réponse, mais confuse), mais ils ont des constructeurs exposés pour vous permettre de créer de nouvelles instances. 

Cet exemple se clone par souci de brièveté, mais vous pouvez voir comment le constructeur pourrait être utilisé pour construire un objet à partir de valeurs définies.

var anonymousType = output.GetType();
var properties = anonymousType.GetProperties();
var propertyTypes = properties.Select(p => p.PropertyType).ToArray();

//The constructor has parameters for each property on the type
var constructor = anonymousType.GetConstructor(propertyTypes);

//clone the existing values to pass to ConstructorInfo
var values = properties.Select(p => p.GetValue(output)).ToArray();
var anonymousClone = constructor.Invoke(values);
0
Jono Stewart

Un moyen facile pourrait être de sérialiser l'objet anonyme dans un Json avec NewtonSoft'JsonConverter (JsonConvert.SerializeObject(anonObject)). Vous pouvez ensuite modifier le Json via la manipulation de chaîne et le resérialiser dans un nouvel objet anonyme que vous pouvez affecter à l'ancienne variable.

Un peu compliqué mais vraiment facile à comprendre pour les débutants!

0
aveschini

Dans un scénario similaire, je devais attribuer un code d'erreur et un message à de nombreux types d'objet regroupant toutes les propriétés imbriquées SHARE, afin de ne pas dupliquer mes méthodes pour référence, en espérant que cela aiderait quelqu'un d'autre:

    public T AssignErrorMessage<T>(T response, string errorDescription, int errorCode)
    {
        PropertyInfo ErrorMessagesProperty = response.GetType().GetProperty("ErrorMessage");
        if (ErrorMessagesProperty.GetValue(response, null) == null)
            ErrorMessagesProperty.SetValue(response, new ErrorMessage());

        PropertyInfo ErrorCodeProperty = ErrorMessagesProperty.GetType().GetProperty("code");
        ErrorCodeProperty.SetValue(response, errorCode);

        PropertyInfo ErrorMessageDescription = ErrorMessagesProperty.GetType().GetProperty("description");
        ErrorMessageDescription.SetValue(response, errorDescription);

        return response;
    }

    public class ErrorMessage
    {
        public int code { get; set; }
        public string description { get; set; }
    }
0
Rakan Murtada