8

I am not sure if it is possible I have seen:
Change Attribute's parameter at runtime.
My case is very similar but I am trying to change the attribute of a class in Runtime:

[Category("Change me")]
public class Classic
{
    public string Name { get; set; }
}

One of the answers was:

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

Changed to more readable format, thanks to Marc Gravell:

TypeDescriptor.AddAttributes(table, new Category{ Name = "Changed" });

All is good when using TypeDescriptor but when using:

var attrs = (Category[])typeof(Classic).GetCustomAttributes(
    typeof(Category),
    true);
attrs[0].Name

Name has the "Change me" text.
Is there a way to change this attribute on runtime?

Edit:
I need this for Linq2Sql in the designer the generated code has the DB schema. I want to use the user's default schema without using XML mapping or change the generated code (the table is still in development stage and changes frequently).

The designer code is:

[global::System.Data.Linq.Mapping.TableAttribute(Name="DbSchema.MyTable")]
public partial class MyTable

I want the attribute to be:

[TableAttribute(Name="MyTable")] 

Now I have dug into the Framework code and I think linq2sql uses:

TableAttribute[] attrs = (TableAttribute[])typeof(MyTable)
   .GetCustomAttributes(typeof(TableAttribute), true);

When I use TypeDescriptor to change the attribute the value isn't changed in GetCustomAttributes.

Community
  • 1
  • 1
Roy Dallal
  • 126
  • 1
  • 9
  • 2
    Why do you want to do this? Attributes are meant to provide metadata, not much else. Why not take the approach of having a "rule list" that is initially populated by the attributes, and changed from there? – vcsjones Jan 27 '11 at 12:52
  • What are you trying to achieve? Localizing text in Category? – Sam B Jan 27 '11 at 13:03
  • @vcsjones believe it or not, there are times where you need to add, change or delete attributes at runtime. I've had to do it in order to add a converter to WPF bindings so that they were serialized instead of being evaluated. –  Jan 27 '11 at 13:07
  • I'm confused - why do you expect changing the `Name` of `Classic` to impact the category? – Marc Gravell Jan 27 '11 at 13:09
  • 1
    Also - that `TypeDescriptor` code itself is horribly brittle; a decent answer there would be to use `TypeDescriptor.AddAttributes` and `TypeDescriptor.GetAttributes` - not to use reflection on attribute instances. – Marc Gravell Jan 27 '11 at 13:16
  • I am trying to change linq2sql Designer attributes - specifically the DB schema to a config value. Designer = [global::System.Data.Linq.Mapping.TableAttribute(Name="DbSchema.MyTable")] – Roy Dallal Jan 27 '11 at 13:42

3 Answers3

2

Avoiding reflection entirely, you can do this via TypeDescriptor:

using System;
using System.ComponentModel;
using System.Linq;
[Category("nice")]
class Foo {  }
static class Program
{
    static void Main()
    {
        var ca = TypeDescriptor.GetAttributes(typeof(Foo))
              .OfType<CategoryAttribute>().FirstOrDefault();
        Console.WriteLine(ca.Category); // <=== nice
        TypeDescriptor.AddAttributes(typeof(Foo),new CategoryAttribute("naughty"));
        ca = TypeDescriptor.GetAttributes(typeof(Foo))
              .OfType<CategoryAttribute>().FirstOrDefault();
        Console.WriteLine(ca.Category); // <=== naughty
    }
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 2
    Well it doesn't really work whenever I use TypeDescriptor I get the changed value. But linq2sql (what I need this for) uses (TableAttribute[])table.GetCustomAttributes(typeof(TableAttribute), true) which gets the original value... – Roy Dallal Jan 27 '11 at 13:33
0

If you use reflection, then not quite like this - reflection attributes can't be substituted - only the component-model view is impacted by TypeDescriptor. However, you can subclass CategoryAttribute to your purposes. Especially useful for i18n.

using System.ComponentModel;
using System;
[MyCategory("Fred")]
class Foo {  }
static class Program
{
    static void Main()
    {
        var ca = (CategoryAttribute)Attribute.GetCustomAttribute(typeof(Foo), typeof(CategoryAttribute));
        Console.WriteLine(ca.Category);
              // ^^^ writes "I was Fred, but not I'm EVIL Fred"
    }
}
class MyCategoryAttribute : CategoryAttribute
{
    public MyCategoryAttribute(string category) : base(category) { }
    protected override string GetLocalizedString(string value)
    {
        return "I was " + value + ", but not I'm EVIL " + value;
    }
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Unfortunately I can't change the original attribute nor create my own version. Linq2Sql expects TableAttribute. – Roy Dallal Jan 27 '11 at 13:55
0

You need to code your attribute so that it supports runtime values. For example, the validation attributes support Internationalization of messages through the setting of a resource type and resource string as opposed to the static Message string.

Another approach is to use a IOC container such as StructureMap or Unity to provide some object/service that provides the values.

If you don;t want to couple your attribute to a particular container, use the Common ServiceLocator wrapper provided by the Patterns and Practices group.

Xhalent
  • 3,914
  • 22
  • 21