在运行时更改属性的参数

我不确定是否可以在运行时更改属性的参数? 例如,在一个程序集中,我有以下类

public class UserInfo { [Category("change me!")] public int Age { get; set; } [Category("change me!")] public string Name { get; set; } } 

这是由第三方供应商提供的类, 我不能更改代码 。 但是现在我发现上面的描述是不准确的,当我将上面的类的实例绑定到属性网格时,我想将“改变我”类别名称更改为别的东西。

我可以知道如何做到这一点?

那么你每天都要学点新东西,显然我撒谎了:

通常不会意识到的是,您可以在运行时相当容易地更改属性实例值。 原因当然是,所创build的属性类的实例是完全正常的对象,可以不受限制地使用。 例如,我们可以得到这个对象:

 ASCII[] attrs1=(ASCII[]) typeof(MyClass).GetCustomAttributes(typeof(ASCII), false); 

…改变其公共variables的值,并显示它已经改变:

 attrs1[0].MyData="A New String"; MessageBox.Show(attrs1[0].MyData); 

…最后创build另一个实例并显示其值不变:

 ASCII[] attrs3=(ASCII[]) typeof(MyClass).GetCustomAttributes(typeof(ASCII), false); MessageBox.Show(attrs3[0].MyData); 

http://www.vsj.co.uk/articles/display.asp?id=713

如果有人沿着这条路走下去,答案是你可以用reflection来做,除非你不能,因为框架中有一个错误。 以下是你如何做到这一点:

  Dim prop As PropertyDescriptor = TypeDescriptor.GetProperties(GetType(UserInfo))("Age") Dim att As CategoryAttribute = DirectCast(prop.Attributes(GetType(CategoryAttribute)), CategoryAttribute) Dim cat As FieldInfo = att.GetType.GetField("categoryValue", BindingFlags.NonPublic Or BindingFlags.Instance) cat.SetValue(att, "A better description") 

一切顺利,除了所有属性的category属性都改变之外,不仅仅是'Age'。

您可以很容易地inheritance大多数常用属性以提供这种可扩展性:

 using System; using System.ComponentModel; using System.Windows.Forms; class MyCategoryAttribute : CategoryAttribute { public MyCategoryAttribute(string categoryKey) : base(categoryKey) { } protected override string GetLocalizedString(string value) { return "Whad'ya know? " + value; } } class Person { [MyCategory("Personal"), DisplayName("Date of Birth")] public DateTime DateOfBirth { get; set; } } static class Program { [STAThread] static void Main() { Application.EnableVisualStyles(); Application.Run(new Form { Controls = { new PropertyGrid { Dock = DockStyle.Fill, SelectedObject = new Person { DateOfBirth = DateTime.Today} }}}); } } 

还有更复杂的选项涉及编写定制的PropertyDescriptor ,通过TypeConverterICustomTypeDescriptorTypeDescriptionProvider公开 – 但这通常是矫枉过正的。

不幸的是,属性并不意味着在运行时改变。 你基本上有两个select:

  1. 使用System.Reflection.Emit即时重新创build类似的types,如下所示。

  2. 请求您的供应商添加此function。 如果您使用的是Xceed.WpfToolkit.Extended,则可以从这里下载源代码,并轻松实现一个接口,如IResolveCategoryName ,它将在运行时parsing该属性。 我做了比这更多的DoubleUpDown ,在PropertyGrid中的DoubleUpDown编辑一个数值时,添加更多的function是非常容易的,等等。

     namespace Xceed.Wpf.Toolkit.PropertyGrid { public interface IPropertyDescription { double MinimumFor(string propertyName); double MaximumFor(string propertyName); double IncrementFor(string propertyName); int DisplayOrderFor(string propertyName); string DisplayNameFor(string propertyName); string DescriptionFor(string propertyName); bool IsReadOnlyFor(string propertyName); } } 

对于第一个选项:然而,这没有适当的属性绑定,以反映结果回到正在编辑的实际对象。

  private static void CreatePropertyAttribute(PropertyBuilder propertyBuilder, Type attributeType, Array parameterValues) { var parameterTypes = (from object t in parameterValues select t.GetType()).ToArray(); ConstructorInfo propertyAttributeInfo = typeof(RangeAttribute).GetConstructor(parameterTypes); if (propertyAttributeInfo != null) { var customAttributeBuilder = new CustomAttributeBuilder(propertyAttributeInfo, parameterValues.Cast<object>().ToArray()); propertyBuilder.SetCustomAttribute(customAttributeBuilder); } } private static PropertyBuilder CreateAutomaticProperty(TypeBuilder typeBuilder, PropertyInfo propertyInfo) { string propertyName = propertyInfo.Name; Type propertyType = propertyInfo.PropertyType; // Generate a private field FieldBuilder field = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private); // Generate a public property PropertyBuilder property = typeBuilder.DefineProperty(propertyName, PropertyAttributes.None, propertyType, null); // The property set and property get methods require a special set of attributes: const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.HideBySig; // Define the "get" accessor method for current private field. MethodBuilder currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + propertyName, getSetAttr, propertyType, Type.EmptyTypes); // Intermediate Language stuff... ILGenerator currGetIl = currGetPropMthdBldr.GetILGenerator(); currGetIl.Emit(OpCodes.Ldarg_0); currGetIl.Emit(OpCodes.Ldfld, field); currGetIl.Emit(OpCodes.Ret); // Define the "set" accessor method for current private field. MethodBuilder currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + propertyName, getSetAttr, null, new[] { propertyType }); // Again some Intermediate Language stuff... ILGenerator currSetIl = currSetPropMthdBldr.GetILGenerator(); currSetIl.Emit(OpCodes.Ldarg_0); currSetIl.Emit(OpCodes.Ldarg_1); currSetIl.Emit(OpCodes.Stfld, field); currSetIl.Emit(OpCodes.Ret); // Last, we must map the two methods created above to our PropertyBuilder to // their corresponding behaviors, "get" and "set" respectively. property.SetGetMethod(currGetPropMthdBldr); property.SetSetMethod(currSetPropMthdBldr); return property; } public static object EditingObject(object obj) { // Create the typeBuilder AssemblyName assembly = new AssemblyName("EditingWrapper"); AppDomain appDomain = System.Threading.Thread.GetDomain(); AssemblyBuilder assemblyBuilder = appDomain.DefineDynamicAssembly(assembly, AssemblyBuilderAccess.Run); ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(assembly.Name); // Create the class TypeBuilder typeBuilder = moduleBuilder.DefineType("EditingWrapper", TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit, typeof(System.Object)); Type objType = obj.GetType(); foreach (var propertyInfo in objType.GetProperties()) { string propertyName = propertyInfo.Name; Type propertyType = propertyInfo.PropertyType; // Create an automatic property PropertyBuilder propertyBuilder = CreateAutomaticProperty(typeBuilder, propertyInfo); // Set Range attribute CreatePropertyAttribute(propertyBuilder, typeof(Category), new[]{"My new category value"}); } // Generate our type Type generetedType = typeBuilder.CreateType(); // Now we have our type. Let's create an instance from it: object generetedObject = Activator.CreateInstance(generetedType); return generetedObject; } } 

你解决了这个问题吗?

这里有可能的步骤来实现可接受的解决scheme。

  1. 尝试创build一个子类,重新定义所有你需要改变[Category]属性的属性(用new标记)。 例:
 public class UserInfo { [Category("Must change")] public string Name { get; set; } } public class NewUserInfo : UserInfo { public NewUserInfo(UserInfo user) { // transfer all the properties from user to current object } [Category("Changed")] public new string Name { get {return base.Name; } set { base.Name = value; } } public static NewUserInfo GetNewUser(UserInfo user) { return NewUserInfo(user); } } void YourProgram() { UserInfo user = new UserInfo(); ... // Bind propertygrid to object grid.DataObject = NewUserInfo.GetNewUser(user); ... } 

稍后编辑: 如果您有大量可能需要重写属性的属性,则该解决scheme的这一部分不可行。 这是第二部分出现的地方:

  1. 当然,如果这个类不是可inheritance的,或者如果你有很多的对象(和属性),这将是无济于事的。 你将需要创build一个完整的自动代理类来获取你的类,并创build一个dynamic类,应用属性,当然使两个类之间的连接..这是一个更复杂一点,但也可以实现。 只要使用reflection,你在正确的道路上。

鉴于PropertyGrid的选定项目是“年龄”:

 SetCategoryLabelViaReflection(MyPropertyGrid.SelectedGridItem.Parent, MyPropertyGrid.SelectedGridItem.Parent.Label, "New Category Label"); 

其中SetCategoryLabelViaReflection()定义如下:

 private void SetCategoryLabelViaReflection(GridItem category, string oldCategoryName, string newCategoryName) { try { Type t = category.GetType(); FieldInfo f = t.GetField("name", BindingFlags.NonPublic | BindingFlags.Instance); if (f.GetValue(category).Equals(oldCategoryName)) { f.SetValue(category, newCategoryName); } } catch (Exception ex) { System.Diagnostics.Trace.Write("Failed Renaming Category: " + ex.ToString()); } } 

至于编程设置select的项目,你想改变的父类别; 有一些简单的解决scheme。 Google“将焦点设置为特定的PropertyGrid属性”。

我真的不这么认为,除非有一些时髦的reflection可以把它关掉。 属性装饰品在编译时设置,据我所知固定

同时,我也得到了一个部分的解决scheme,来源于以下文章:

  1. ICustomTypeDescriptor,第1部分
  2. ICustomTypeDescriptor,第2部分
  3. 在运行时向PropertyGrid添加(删除)项目

基本上你会创build一个generics类CustomTypeDescriptorWithResources<T> ,这将通过reflection获取属性,并从文件加载DescriptionCategory (我想你需要显示本地化的文本,所以你可以使用资源文件( .resx ))

您可以在运行时在Class级别(不是对象)更改属性值:

 var attr = TypeDescriptor.GetProperties(typeof(UserContact))["UserName"].Attributes[typeof(ReadOnlyAttribute)] as ReadOnlyAttribute; attr.GetType().GetField("isReadOnly", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(attr, username_readonly); 

这是一个“秘密”的方式来做到这一点:

如果属性参数具有固定数量的常量潜在值,则可以为参数的每个可能值定义一个单独的属性(并为每个属性指定稍微不同的属性),然后切换您dynamic引用的属性。

在VB.NET中,它可能看起来像这样:

 Property Time As Date <Display(Name:="Month")> ReadOnly Property TimeMonthly As Date Get Return Time End Get End Property <Display(Name:="Quarter")> ReadOnly Property TimeQuarterly As Date Get Return Time End Get End Property <Display(Name:="Year")> ReadOnly Property TimeYearly As Date Get Return Time End Get End Property