给定以下代码:
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属性?
我想在它被设置为新值后更新它。(用例是不需要指定Order,而是自动为Order分配一个值,以匹配MyObject上的属性从上到下出现的顺序)
正如斯基特先生在回答中所写的那样,据我所知,这是不可能做到的。您可以更新Order值,但它不会持续存在:
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
}
}
}
}
更新答案:因为属性是"静态元数据",所以编译后不能持久地更改它们。(可以在c#中动态添加属性吗?)
因此,实现OP对进行持久属性值更改的请求的一种方法是在运行时编译代码。如果这是需要的,那么可能值得认真考虑使用不同的方法来解决您正在尝试解决的问题,因为这意味着您需要做更多的工作(但对我来说这是非常酷的)。
也有可能使用叫做TypeBuilder的东西(我如何在c#运行时向类添加属性?)。我不熟悉这个,但看起来很有希望。它看起来也像使用了运行时编译。
运行时编译:
这是一种可以完成的方法,通过在运行时编译代码。(像OP那样使用NUnit,而不是XUnit):
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);
}
}
}
与其这样做
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; }
}