35

这是我的代码,例如:

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

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

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

谢谢。

4

8 回答 8

46

匿名类型属性是只读的,不能设置。

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

匿名类型(C# 编程指南)

于 2013-07-03T06:53:56.373 回答
33

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

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

此方法依赖于编译器用于命名这些支持字段的特定约定:<xxxxx>i__Field在 .NET 和<xxxxx>Mono 中,使用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 }

可以在此处找到更详细的版本

于 2015-05-14T16:18:34.090 回答
10

如果您遇到需要可变类型的情况,而不是弄乱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;
}
于 2015-05-25T05:05:37.973 回答
2

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

其他答案正确地表明它们是不可变对象(尽管亚历克斯的答案确实显示了如何获取支持字段,这是一个很好但混乱的答案)但它们确实暴露了构造函数,因此您可以创建新实例。

下面的示例采用模板实例并在 CreateFromAnonymousTemplate 函数中创建它的新实例。有缺点,但最大的好处(除了能够创建这些对象!)是你坚持匿名类型应该是不可变的约定。

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);
    }
}
于 2017-05-08T11:28:43.123 回答
1

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

    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; }
    }
于 2016-08-24T18:57:08.753 回答
1

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

有点复杂,但对于初学者来说真的很容易理解!

于 2017-09-26T11:26:35.177 回答
1

我来到这里是对Anonymous Types感到好奇,结果从给出的答案中学到了很多东西。

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


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

为了更好地理解原因,值得阅读@EricLippert 在对另一个关于匿名类型的问题的评论中所说的话:

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

加上官方 C# 文档Accepted Answer引用的文本:

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

为了说明为什么不能(开箱即用)设置匿名类型对象的值,让我们看看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 };

并且希望能够修改花花公子的后一个属性,您可以(在许多情况下)简单地通过动态更改 var 关键字。然后你可以做

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

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

但要小心!var 和 dynamic 并不像乍一看那样相似。正如@MikeBeaton 对@BK 的评论中已经指出的那样

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

有一些关于 dynamic vs var 的帖子

于 2019-11-08T21:29:19.903 回答
0

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

于 2013-07-03T06:58:16.590 回答