如何设置匿名对象的属性值?

时间:2013-07-03 06:47:42

标签: c# .net anonymous-types

这是我的代码:

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

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

它发生异常:“找不到属性集方法”。我想知道如何创建一个具有可设置属性的匿名类型。

感谢。

8 个答案:

答案 0 :(得分:30)

匿名类型属性是只读的,无法设置。

  

匿名类型提供了一种方便的方法来封装一组   只读属性到一个对象而不必显式   首先定义一个类型。类型名称由编译器生成,并且是   在源代码级别不可用。每个属性的类型是   由编译器推断。

Anonymous Types (C# Programming Guide)

答案 1 :(得分:20)

  

如何设置匿名对象的属性值?

因为今天我被提醒,当使用反射结合知识来实现​​某些事情时,没有什么是真正不可变的(在这种情况下支持匿名类型的只读属性的字段),我认为添加一个是明智的回答说明如何通过将匿名对象的属性值映射到其支持字段来更改其属性值。

此方法依赖于编译器用于命名这些支持字段的特定约定:.NET中的<xxxxx>i__Field和Mono上的<xxxxx>xxxxx表示属性名称。如果要更改此约定,则下面的代码将失败(注意:如果您尝试将其提供给非匿名类型,它也将失败)。

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

样品:

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

输出:

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

可以找到更精细的版本here

答案 2 :(得分:5)

如果您遇到需要可变类型的情况,而不是使用Anonymous类型,则可以使用ExpandoObject

示例

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

答案 3 :(得分:1)

匿名类型在C#中是不可变的。我认为你不能改变那里的财产。

答案 4 :(得分:0)

我有一个类似的场景,我需要将错误代码和消息分配给所有SHARE特定嵌套属性的众多对象类型,因此我不必复制我的方法以供参考,希望它可以帮助其他人:

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

答案 5 :(得分:0)

建议:您可以一次设置所有属性。

其他答案正确地表明它们是不可变对象(虽然Alex的答案确实显示了如何获得支持字段,这是一个很好但又很混乱的答案)但它们确实暴露了构造函数,所以你可以创建新实例。

为简洁起见,此示例克隆了自身,但您可以看到如何使用构造函数从定义的值构建对象。

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

答案 6 :(得分:0)

一种简单的方法可以是使用NewtonSoft'JsonConverter(JsonConvert.SerializeObject(anonObject))序列化Json中的匿名对象。然后,您可以通过字符串操作更改Json并将其重新序列化为可以分配给旧变量的新匿名对象。

对于初学者来说有点卷积但很容易理解!

答案 7 :(得分:0)

我来到这里的时候是对匿名类型感到好奇,并从给出的答案中学到了很多东西。

在我的答案中,我将尝试综合发现的一些有价值的信息,并提供其他答案中未发现的信息,并且我认为这些信息也值得将来的访问者阅读。


为什么不能(开箱即用)重新设置匿名类型对象的值?

为更好地理解原因,值得阅读@EricLippert在对another question about Anonymous Types的评论中所说的话:

  

请注意,VB中的匿名类型允许部分突变。在VB中,您需要声明匿名类型的哪些部分是可变的;生成的代码将不会使用可变位作为哈希代码/等式的一部分,因此您不会遇到“字典丢失”的问题。我们决定不在C#中实现这些扩展。

加上官方C#文档中的接受的答案的文字cited

  

匿名类型提供了一种方便的方法,可以将一组只读属性封装到单个对象中,而不必先明确定义一个类型。类型名称由编译器生成,在源代码级别不可用。每个属性的类型由编译器推断。

只是为了说明为什么无法(开箱即用)设置匿名类型对象的值的原因,让我们看看C# in a Nutshell的含义:

[如果您定义]

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

编译器将此翻译为(大约)以下内容:

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

但是,一旦设置了匿名类型的值,就无法对其进行修改吗?

好吧,我们可以从@Alex给出的答案中学到:

  

将反射与实现某些特定事物的知识结合使用时,什么都不是真正不变的(在这种情况下,匿名类型的只读属性的备用字段)。

如果您想知道如何修改匿名类型对象的值,请阅读他的回答,那真的很值得!


如果最后,您会坚持使用简单的内衬

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

,并且希望能够修改dude属性的后一个,可以(在许多情况下)只需通过动态更改var关键字即可。那你可以做

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

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

但是要当心! var和dynamic不像初看起来那样相似。正如在@ B.K。的评论中已经提到的那样。通过@MikeBeaton

  

这在您需要输入空值的情况下不起作用? (即ExpandoObject不支持,但匿名类型可以支持)

有一些SO posts about dynamic vs var