给出以下代码:
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Xunit;
namespace Company.Tests
{
public class MyObject
{
[Display(Order = 1000)]
public virtual string StringPropertyB { get; set; }
[Display(Order = 2000)]
public virtual string StringPropertyA { get; set; }
}
public class MyObjectTest
{
[Fact]
public void X()
{
var properties = typeof(MyObject).GetProperties();
var stringPropertyBPropertyInfo = properties[0];
var stringPropertyAPropertyInfo = properties[1];
// Debugger Display = "{[System.ComponentModel.DataAnnotations.DisplayAttribute(Order = 1000)]}"
var bDisplayAttribute = stringPropertyBPropertyInfo.GetCustomAttributesData().FirstOrDefault();
// Debugger Display = "{[System.ComponentModel.DataAnnotations.DisplayAttribute(Order = 2000)]}"
var aDisplayAttribute = stringPropertyAPropertyInfo.GetCustomAttributesData().FirstOrDefault();
}
}
}
如何以编程方式更新Order属性?
我想在将其设置为新值后更新它。 (用例不必指定订单,而是自动为订单分配一个值,以匹配MyObject上的属性从上到下显示的顺序。)
答案 0 :(得分:1)
正如Skeet先生在答案中写道,Thumper指出,就我所知,这是无法做到的。您可以更新订单值,但不会保留:
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using Shouldly;
using Xunit;
namespace Tests
{
public class ObjectWithDisplayOrder
{
[Display(Order = 0)]
public virtual string StringPropertyB { get; set; }
[Display(Order = 0)]
public virtual string StringPropertyA { get; set; }
}
public class DisplayOrderTests
{
[Fact]
public void ShouldUpdateDisplayOrderProperty()
{
const int updatedOrderValue = 1000;
var properties = typeof(ObjectWithDisplayOrder).GetProperties();
foreach (var property in properties)
{
var displayAttribute = (DisplayAttribute) property.GetCustomAttributes().First(a => a is DisplayAttribute);
var props = displayAttribute.GetType().GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public).ToList();
props.Single(p => p.Name == "Order").SetValue(displayAttribute, updatedOrderValue);
// displayAttribute Order is 1000 here, but it's not persisted...
}
foreach (var property in properties)
{
var displayAttribute = (DisplayAttribute) property.GetCustomAttributes().First(a => a is DisplayAttribute);
displayAttribute.GetOrder().ShouldBe(updatedOrderValue); // Fails - Order is still 0
}
}
}
}
答案 1 :(得分:0)
而不是你做了什么
public class MyObject
{
[DefaultValue(1000)]
public virtual int StringPropertyBOrder { get; set; }
public virtual string StringPropertyB { get; set; }
[DefaultValue(2000)]
public virtual int StringPropertyAOrder { get; set; }
public virtual string StringPropertyA { get; set; }
}
答案 2 :(得分:0)
更新答案: 因为属性是"静态元数据",所以在编译后不能持久地更改它们。 (Can attributes be added dynamically in C#?)
因此,实现OP对属性值进行持久更改的请求的一种方法是在运行时编译代码。如果这是所需要的,那么可能值得认真考虑一下你试图解决的问题的不同方法,因为这对你来说意味着更多的工作(但这对我来说非常酷)。
还有可能使用名为 TypeBuilder (How Can I add properties to a class on runtime in C#?)的东西。我对此并不熟悉,但看起来很有希望。它看起来也像是使用运行时编译。
运行时编译:
这是通过在运行时编译代码来实现的一种方法。 (使用NUnit,而不是XUnit,正如OP所做的那样):
namespace OpUnitTest {
[TestClass]
public class OpTest{
//Use some web templating model so we can easily change it later (#=variable#)
string myClassToCompile = @"
using System.ComponentModel.DataAnnotations;
namespace Test {
public class ObjectWithDisplayOrder {
[Display(Order = #=0#)]
public virtual string StringPropertyB { get; set; }
[Display(Order = #=1#)]
public virtual string StringPropertyA { get; set; }
}
}
";
[TestMethod]
public void AssignAtributeValuesDynamically() {
const int order = 1000;
//Escape curly braces.
myClassToCompile = myClassToCompile.Replace("{", "{{").Replace("}", "}}");
//We could use Regex, or even a for loop, to make this more-elegant and scalable, but this is a Proof of Concept.
myClassToCompile = myClassToCompile.Replace("#=0#", "{0}").Replace("#=1#", "{1}");
myClassToCompile = string.Format(myClassToCompile, order, order);
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.ReferencedAssemblies.Add("System.ComponentModel.DataAnnotations.dll");
parameters.GenerateInMemory = true;
CompilerResults results = provider.CompileAssemblyFromSource(parameters, myClassToCompile);
//You would normally check for compilation errors, here.
Assembly assembly = results.CompiledAssembly;
Type myCompiledObject = assembly.GetType("Test.ObjectWithDisplayOrder");
PropertyInfo[] properties = myCompiledObject.GetProperties();
foreach (var property in properties) {
var displayAttribute = (DisplayAttribute)property.GetCustomAttributes().First(a => a is DisplayAttribute);
Assert.AreEqual(order, displayAttribute.GetOrder());
}
}
}
关于C#中运行时编译的一个很好的提升指南(这是我的一点热情):http://www.codeproject.com/Tips/715891/Compiling-Csharp-Code-at-Runtime
原始答案:
从技术上讲,它表明您确实可以更改属性的值,但正如OP指出的那样 - 这并不会持续存在。
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Xunit;
namespace Company.Tests
{
public class MyObject
{
[Display(Order = 1000)]
public virtual string StringPropertyB { get; set; }
[Display(Order = 2000)]
public virtual string StringPropertyA { get; set; }
}
public class MyObjectTest
{
[Fact]
public void X()
{
var properties = typeof(MyObject).GetProperties();
var stringPropertyBPropertyInfo = properties[0];
var bDisplayAttribute = (DisplayAttribute)stringPropertyBPropertyInfo.GetCustomAttributes().First();
var props = bDisplayAttribute.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public).ToList();
props.Single(p => p.Name == "Order").SetValue(bDisplayAttribute, 5);
}
}
}