views:

2944

answers:

8

Hi,

I am not sure whether is it possible to change attribute's parameter during runtime? For example, inside an assembly I have the following class

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

This is a class that is provided by a third party vendor and I can't change the code. But now I found that the above descriptions are not accurate, and I want to change the "change me" category name to something else when i bind an instance of the above class to a property grid.

May I know how to do this?

+2  A: 

Can you derive from the classes in question and provide new versions of the properties with new attributes?

Brad Wilson
A: 

I really don't think so, unless there's some funky reflection that can pull it off. The property decorations are set at compile time and to my knowledge are fixed

Glenn Slaven
+4  A: 

Well you learn something new every day, apparently I lied:

What isn’t generally realised is that you can change attribute instance values fairly easily at runtime. The reason is, of course, that the instances of the attribute classes that are created are perfectly normal objects and can be used without restriction. For example, we can get the object:

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

…change the value of its public variable and show that it has changed:

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

…and finally create another instance and show that its value is unchanged:

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

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

Glenn Slaven
A: 

Hi Glenn,

Your link is nice enough, and almost just solve my problem. However the Category attribute in my case is read-only, which means that you can change it.

I've found another link that discusses about this. Anyone has a better idea?

Ngu Soon Hui
A: 

Hi,

Did you solve the problem?

Here are possible steps to achieve an acceptable solution.

  1. Try to create a child class, redefine all of the properties that you need to change the [Category] attribute (mark them with new). Example:
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);

...

}

Later Edit: This part of the solution is not workable if you have a large number of properties that you might need to rewrite the attributes. This is where part two comes into place:

  1. Of course, this won't help if the class is not inheritable, or if you have a lot of objects (and properties). You would need to create a full automatic proxy class that gets your class and creates a dynamic class, applies attributes and of course makes a connection between the two classes.. This is a little more complicated, but also achievable. Just use reflection and you're on the right road.
Bogdan Maxim
Bogdan,I afraid that subclassing the class and doing all the refinition is impractical, to say the least.
Ngu Soon Hui
If you are subclassing, then you would have to automatically recreate all the attributes, and replace the old attributes. This is the easiest solution if you can subclass. The main idea was to create a proxy automatically (using a dynamic type), and replace the attributes on the fly.
Bogdan Maxim
A: 

Hi, In the mean time I've come to a partial solution, derived from the following articles:

  1. ICustomTypeDescriptor, Part 1
  2. ICustomTypeDescriptor, Part 2
  3. Add (Remove) Items to (from) PropertyGrid at Runtime

Basically you would create a generic class CustomTypeDescriptorWithResources<T>, that would get the properties through reflection and load Description and Category from a file (I suppose you need to display localized text so you could use a resources file (.resx))

Bogdan Maxim
A: 

You can subclass most of the common attributes quite easily to provide this extensibility:

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

There are more complex options that involve writing custom PropertyDescriptors, exposed via TypeConverter, ICustomTypeDescriptor or TypeDescriptionProvider - but that is usually overkill.

Marc Gravell
+1  A: 

In case anyone else walks down this avenue, the answer is you can do it, with reflection, except you can't because there's a bug in the framework. Here's how you would do it:

    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")

All well and good, except that the category attribute is changed for all the properties, not just 'Age'.

Jules