Saturday, September 21, 2013

[C#] Class attributes vs. interface's properties

In C#, attributes are meta-information attached to a code entity (fields/properties, types, or methods). Attributes are generally very useful. However, are they useful on classes? One could argue that instead of using attributes, we can just have that class implement some interface's properties. See the code below for example. Eric Lippert has also advocated for using properties.

Here, I'd like to make a stronger case for using class attributes.
  1. Readability: an attribute indicates that the meta-information is static, i.e. it is bound to the class, not instances of that class.
  2. Interface doesn't support default values for properties. So any type that implements that interface has to implement all the properties. We can make the properties default in (abstract) classes but not in interfaces but C# (reasonably) doesn't support multiple class inheritance.
  3. Attribute may contain logic (see the last example below) while an interface cannot. Furthermore, the same attribute (and any embedded logic) may be used for both type and fields/properties.
Of course, if the meta information is frequently accessed by other code, then we absolutely should use properties because accessing properties is less taxing and also faster (see the example below). 

  1 /* Using attribute */ 
  2     class FriendlyNamesAttribute : Attribute
  3     { 
  4         public string ShortName;
  5  
  6         public string LongName;
  7  
  8         public string HelpText;
  9     } 
 10  
 11     [FriendlyNames(ShortName = "miner") 
 12     class DataMiner  
 13     {  
 14         ... // Class logic here 
 15     } 
 16  
 17     // Accessing the meta-information 
 18     var attr = obj.GetType().GetCustomAttribute<FriendlyNamesAttribute>(); 
 19     if (attr != null) 
 20     { 
 21         var shortName = attr.ShortName;
 22         ... 
 23     } 
 24  
 25 /* Using Properties */ 
 26     interface IHasFriendlyNames  
 27     { 
 28         public string ShortName { get; }
 29         public string LongName { get; }
 30         public string HelpText { get; }
 31     } 
 32  
 33     class DataMiner : IHasFriendlyNames
 34     { 
 35         ... // Class logic here 
 36  
 37         public string ShortName { get { return "Miner"; } } 
 38  
 39         public string LongName { get { return null; } }
 40  
 41         public string HelpText { get { return null; } }
 42     } 
 43  
 44     var friendlyObj = obj as IHasFriendlyName;
 45     if (friendlyObj != null)
 46     { 
 47         var shortName = friendlyObj.ShortName;
 48         ... 
 49     } 
 50  
 51 /* A slightly more complex FriendlyName attribute */
 52     public abstract class NameAttribute : Attribute
 53     { 
 56         public string ShortName; 
 57         public string LongName;
 59  
 60         private static Regex _shortRegex = new Regex("[a-z]"); 
 61         private static Regex _longRegex = new Regex("(?<=[a-z])([A-Z])");
 62  
 63         /// <summary> 
 64         /// Given a field/class name, automatically shortify it
 65         /// </summary> 
 66         public string GetShortName(string rawName)
 67         { 
 70             ShortName = ShortName ?? _shortRegex.Replace(rawName, "");
 73             return ShortName; 
 74         } 
 75  
 76         /// <summary> 
 77         /// Given a field/class name, automatically prettify it
 78         /// </summary> 
 79         public string GetLongName(string rawName)
 80         { 
 83             LongName = LongName ?? _longRegex.Replace(rawName, " $1");
 86             return LongName; 
 87         } 
 88     }