Pergunta

this is my code for example:

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

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

It occurs an exception: "Property set method not found". I want to know how to create an anonymous type with properties which can be set.

Thanks.

Foi útil?

Solução

Anonymous type properties are read only and they cannot be set.

Anonymous types provide a convenient way to encapsulate a set of read-only properties into a single object without having to explicitly define a type first. The type name is generated by the compiler and is not available at the source code level. The type of each property is inferred by the compiler.

Anonymous Types (C# Programming Guide)

Outras dicas

How to set value for property of an anonymous object?

Because I was reminded today that nothing is truly immutable when using reflection in combination with knowledge on how certain things are implemented (backing fields for the read-only properties of anonymous types in this case), I thought it wise to add an answer illustrating how the property values of an anonymous object can be changed, by mapping them to their backing fields.

This method relies on a specific convention used by the compiler for naming these backing fields: <xxxxx>i__Field in .NET and <xxxxx> on Mono, with the xxxxx representing the property name. If this convention were to change, the code below will fail (note: it will also fail if you try to feed it something that is not an anonymous type).

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

Sample:

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

With output:

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

A slightly more elaborate version can be found here

If you ever come across a situation where you need a mutable type, instead of messing around with the Anonymous type, you can just use the ExpandoObject:

Example:

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

A suggestion: you can set all properties at once.

The other answers correctly suggest that they are immutable objects (although Alex's answer does show how to get at the backing fields, which is a good, yet messy answer) but they do have constructors exposed, so you can create new instances.

Below, the example takes a template instance and creates new instances of it in the CreateFromAnonymousTemplate function. There are downsides, but the biggest benefit (aside from being able to create these objects!) is that you are sticking to the convention that anonymous types should be immutable.

class Program
{
    static void Main(string[] args)
    {
        // Create a template that defines the anonymous type properties.
        var personTemplate = new { Name = "", Age = 0 };

        var sam = CreateFromAnonymousTemplate(personTemplate, "Sam", 43);
        var sally = CreateFromAnonymousTemplate(personTemplate, "Sally", 24);
    }

    private static Dictionary<Type, ConstructorInfo> _constructors = new Dictionary<Type, ConstructorInfo>();

    // By using T, we get intellisense for our returned objects.
    static T CreateFromAnonymousTemplate<T>(T templateReference, params object[] propertyValues)
    {
        // This is the type of the template. In this case, the anonymous type.
        Type anonymousType = templateReference.GetType();

        ConstructorInfo anonymousTypeConstructor;

        if (_constructors.ContainsKey(anonymousType))
        {
            anonymousTypeConstructor = _constructors[anonymousType];

            if(anonymousTypeConstructor.GetParameters().Length != propertyValues.Length)
                throw new ArgumentException("Invalid initialisation properties. Parameters must match type and order of type constructor.", "propertyValues");
        }
        else
        {
            PropertyInfo[] properties = anonymousType.GetProperties();
            if (properties.Count() != propertyValues.Length)
                throw new ArgumentException("Invalid initialisation properties. Parameters must match type and order of type constructor.", "propertyValues");

            // Retrieve the property types in order to find the correct constructor (which is the one with all properties as parameters).
            Type[] propertyTypes = properties.Select(p => p.PropertyType).ToArray();

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

            // We cache the constructor to avoid the overhead of creating it with reflection in the future.
            _constructors.Add(anonymousType, anonymousTypeConstructor);
        }

        return (T)anonymousTypeConstructor.Invoke(propertyValues);
    }
}

I had a similar scenario where I needed to assign an error code and message to numerous object types that all SHARE specific nested properties so I don't have to duplicate my methods for reference hoping it helps someone else:

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

An easy way could be to serialize the anonymous object in a Json with NewtonSoft'JsonConverter (JsonConvert.SerializeObject(anonObject)). Then you can change the Json via string manipulation and reserialize it into a new anonymous object that you can assign to the old variable.

A little convolute but really easy to understand for beginners!

I came here being curious about Anonymous Types, and turned out learning a lot from the answers given.

In my answer i will try to synthesize some valuable information i found, and provide information that i did not see in the other answers and that i think will also worth to read for future visitors.


Why is not possible to re-set (out of the box) the values of an anonymously type object?

To better understand the why, it is worth to read what @EricLippert said in a comment to another question about Anonymous Types:

Note that anonymous types in VB are allowed to be partially mutated. In VB you get to state which parts of the anonymous type are mutable; the generated code will not use mutable bits as part of a hash code / equality, so you don't get the "lost in the dictionary" problem. We decided to not implement these extensions in C#.

Plus the text cited by the Accepted Answer from the official C# documentation:

Anonymous types provide a convenient way to encapsulate a set of read-only properties into a single object without having to explicitly define a type first. The type name is generated by the compiler and is not available at the source code level. The type of each property is inferred by the compiler.

And just to justify a bit the why it is not possible (out of the box) to set the value of a anonymously typed object, let's see what C# in a Nutshell says:

[If you define]

var dude = new { Name = "Bob", Age = 23 };

The compiler translates this to (approximately) the following:

internal class AnonymousGeneratedTypeName
{

    private string name; // Actual field name is irrelevant
    private int    age;  // Actual field name is irrelevant

    public AnonymousGeneratedTypeName(string name, int age)
    {
        this.name = name; this.age = age;
    }

    public string Name { get { return name; } }

    public int    Age  { get { return age;  } }

    // The Equals and GetHashCode methods are overriden...
    // The ToString method is also overriden.

}
...

var dude = AnonymousGeneratedTypeName ("Bob", 23);

But, is it true that once you set the value of an Anonymous Type it is not possible to modify it?

Well, as we can learn from the answer given by @Alex:

nothing is truly immutable when using reflection in combination with knowledge on how certain things are implemented (backing fields for the read-only properties of anonymous types in this case).

If you are curious to see HOW you can modify the value of an anonymously typed object, go and read his answer, it is really worth!


If at the end, you what to stick with the simplicity in the one liner

var dude = new { Name = "Bob", Age = 23 };

and want to be able to modify latter one of dude's properties, you can (in many cases) simply change the var keyword by dynamic. Then you can do

dynamic dude = new { Name = "Bob", Age = 23 };

dude.Name = "John"; // Compiles correctly.

But watch out! var and dynamic are not as similar as they seem at a first look. As already noted in a comment to @B.K. by @MikeBeaton

this doesn't work in the case where you need a typed null value? (i.e. an ExpandoObject can't support that, but an anonymous type can)

There are some SO posts about dynamic vs var.

Anonymous types are immutable in C#. I don't think you can change the property there.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top