在运行时更改Attribute的参数

时间:2008-09-09 05:55:53

标签: c# reflection attributes

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

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

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

我可以知道怎么做吗?

10 个答案:

答案 0 :(得分:27)

你每天都学到新东西,显然我撒了谎:

  

通常没有意识到的是   您可以公平地更改属性实例值   很容易在运行时。原因是,   当然,那个例子   创建的属性类是   完全正常的物体,可以   使用不受限制。例如,   我们可以得到这个对象:

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

...更改其公共变量的值并显示它已更改:

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

...最后创建另一个实例   并表明其价值不变:

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

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

答案 1 :(得分:7)

如果其他人沿着这条大道走下去,答案是你可以用反射来做,除非你不能,因为框架中有一个错误。这是你如何做到的:

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'。

答案 2 :(得分:3)

您可以非常轻松地将大多数常见属性子类化,以提供此可扩展性:

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公开 - 但这通常是矫枉过正。

答案 3 :(得分:2)

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

  1. 使用System.Reflection.Emit动态重新创建类似的类型,如下所示。

  2. 要求您的供应商添加此功能。如果您使用的是Xceed.WpfToolkit.Extended,则可以从here下载源代码,并轻松实现可在运行时解析该属性的IResolveCategoryName接口。我做了更多,在DoubleUpDownPropertyGrid内编辑数值时,添加更多功能(如限制)非常容易。

    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);
        }
    }
    
  3. 对于第一个选项:但是缺少适当的属性绑定以将结果反映回正在编辑的实际对象。

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

答案 4 :(得分:1)

你解决了这个问题吗?

以下是实现可接受解决方案的可能步骤。

  1. 尝试创建子类,重新定义更改[Category]属性所需的所有属性(用new标记它们)。例如:
  2. 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);
    
    ...
    
    }
    

    稍后编辑: 如果您有可能需要重写属性的大量属性,则此部分解决方案无法使用。这是第二部分到位的地方:

    1. 当然,如果该类不可继承,或者您有很多对象(和属性),这将无济于事。您需要创建一个全自动代理类来获取您的类并创建一个动态类,应用属性,当然在两个类之间建立连接。这有点复杂,但也可以实现。只需使用反射,你就在正确的道路上。

答案 5 :(得分:1)

鉴于PropertyGrid的所选项目是&#34;年龄&#34;:

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

以编程方式设置所选项目,您希望更改的父类别;有许多简单的解决方案。 Google&#34;将焦点设置为特定的PropertyGrid属性&#34;。

答案 6 :(得分:0)

我真的不这么认为,除非有一些时髦的反思可以把它拉下来。属性装饰在编译时设置,据我所知是固定的

答案 7 :(得分:0)

与此同时,我得出了一个部分解决方案,来自以下文章:

  1. ICustomTypeDescriptor, Part 1
  2. ICustomTypeDescriptor, Part 2
  3. Add (Remove) Items to (from) PropertyGrid at Runtime
  4. 基本上你会创建一个通用类CustomTypeDescriptorWithResources<T>,通过反射获取属性并从文件加载DescriptionCategory(我想你需要显示本地化的文本,所以你可以使用资源文件(.resx))

答案 8 :(得分:0)

这是一种“欺骗”的方式:

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

在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

答案 9 :(得分:-1)

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

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