0

I got myself in a situation where using the System.Attribute class seemed (at first glance) to be a good idea.

I have an object to be printed in my application, and I need a label before each property (or just a string before it). I could put each property hardcoded like:

Console.WriteLine("Color:"+obj.color);
Console.WriteLine("Size:"+obj.size);

And so on for each property. But instead of it, I was trying to create a code where this 'label' doesn't needed to be hardcoded, so I could print every property dynamically.

I got something like that, using System.Attribute class:

public class MyObject 
{
    [MyCustomLabel("Color:")]
    public string Color;

    [MyCustomLabel("Size:")]
    public string Size;
    //etc...
}

So here comes my problem: retrieving this Attribute's value is not impossible, but it's not friendly since I had to use some reflection for it.

I'm not really scared of using reflection, but it seemed that I was using attributes for something that it wasn't created for.

I wonder where are the best places for using attributes, and if this is really a situation for using it.

Myles Gray
  • 8,711
  • 7
  • 48
  • 70
mkato
  • 402
  • 4
  • 11
  • 4
    What are you going to do when someone asks you to ship a French version of your application? Put user-facing "magic strings" into resources so that you can easily pull them out and localize them, not into attributes where they are "baked in" to the metadata. – Eric Lippert Jun 30 '09 at 04:17
  • Some more thoughts on properties vs attributes: http://blogs.msdn.com/ericlippert/archive/2009/02/02/properties-vs-attributes.aspx – Eric Lippert Jun 30 '09 at 04:18
  • Why don't you like reflection? – Jay Bazuzi Jun 30 '09 at 04:52
  • I don't understand Eric Lippert's comment. Attributes can also use localizable resources: for example the internal System.Windows.Forms.SRDescriptionAttribute, which inherits from System.ComponentModel.DescriptionAttribute, gets a string from resources. – Joe Jun 30 '09 at 06:37
  • Sure. But why go through all that rigamarole? What does it buy you? – Eric Lippert Jun 30 '09 at 07:03
  • Eric Lippert: Actually, I thought about multi-language too. I'm using the magic strings just like you said. Jay Bazuzi: Not that I don't like reflection, I like it a lot (but still beggining with it on C#). What I thought is: if isn't there a coder-friendly way to retrive the Attributes, maybe they were not meant to be retrieved. But I liked what Scott Weinstein said: Attributes and reflection go hand in hand. So I'll check more about them thinking on this approach – mkato Jun 30 '09 at 11:55
  • Or maybe I didn't find the right way to retrieve them =) – mkato Jun 30 '09 at 11:56
  • 1
    "But why go through all that rigamarole? What does it buy you?" - Ask the .NET team that implemented it. In their case I suspect it was mainly so that intellisense in the VS properties window could display localized descriptions. – Joe Jul 23 '09 at 20:42

5 Answers5

4

Attributes and reflection go hand in hand. With the exception of some compiler/runtime attributes, there's no way to use them with out reflecting over the code.

That said, your approach is reasonable, and you might want to take a look at the attributes in the System.ComponentModel namespace which have a number of classes to decorate properties with useful metadata.

Scott Weinstein
  • 18,890
  • 14
  • 78
  • 115
1

You're on the right track.

Also, there is a [DisplayName] attribute already tailor-made for this purpose, which has been in .NET since 2.0.

http://msdn.microsoft.com/en-us/library/system.componentmodel.displaynameattribute.aspx

Brad Wilson
  • 67,914
  • 9
  • 74
  • 83
1

If you're just writing to the console, i.e. it's debugging style output then what you want is minimal chance of typo/copy paste error.

This is confusing under the hood but is highly effective at the call sites:

public static void WriteNameAndValue<T,TValue>(
    this TextWriter tw, T t,
    Expression<Func<T, TValue>> getter)
{
    var memberExpression = getter.Body as MemberExpression;
    if (memberExpression == null)
        throw new ArgumentException("missing body!");
    var member = memberExpression.Member;
    tw.Write(member.Name);
    tw.Write(": ");
    if (member is FieldInfo)
    {
        tw.Write(((FieldInfo)member).GetValue(t));
    }
    else if (member is PropertyInfo)
    {
        tw.Write(((PropertyInfo)member).GetValue(t, null));
    }
}


public static void WriteNameAndValueLine<T,TValue>(
    this TextWriter tw, T t,
    Expression<Func<T, TValue>> getter)
{

    WriteNameAndValue<T,TValue>(tw, t, getter);
    tw.WriteLine();
}

then you can write

t.Foo = "bar";
t.Bar = 32.5;
Console.Out.WriteNameAndValueLine(t, x => x.Foo);
Console.Out.WriteNameAndValueLine(t, x => x.Bar);
// output
// Foo: bar
// Bar: 32.5

If you want this to be more configurable at runtime via resources and with considerations for localization you can do so but I would consider another, more standardized, approach instead if this was a likely requirement.

P.S. if you wanted to get fancy you could replace the FieldInfo/PropertyInfo switch with

tw.Write(getter.Compile()(t));

and then you could check for MethodInfo in the expression as well (or allow arbitrary lambdas and just insert the line number or some other generic text. I suggest not going down this route, the usage is already confusing also this may cause unwanted load in what should be a simple logging method.

ShuggyCoUk
  • 36,004
  • 6
  • 77
  • 101
0

this is exactly how Serialization works so I would say your approach is reasonable. Another way you can approach this is to create a dictionary of PropertyNames and their Titles and then look up the title based on the property name.

Stan R.
  • 15,757
  • 4
  • 50
  • 58
0

I have an object to be printed in my application, and I need a label before each property (or just a string before it).

Why do you think of using attributes in your case? Where do you print this objects in your application.

Maybe just implement ToString in your object(s)
E.g.

public override string ToString()
{
    StringBuilder sb = new StringBuilder();

    sb.Append("PropertyX: ");
    sb.AppendLine(this.PropertyX);

    // get string from resource file
    sb.Append(Resources.FileName);
    sb.Append(": ");
    sb.AppendLine(this.FileName);

    sb.Append("Number: ");
    sb.AppendLine(this.Number.ToString());

    return sb.ToString();
}
Peter Gfader
  • 7,673
  • 8
  • 55
  • 56