tags:

views:

269

answers:

4

I understand that they are compile time, so they can't be generic, and must be initialized with constant values. But:
Why can't they have information you would get if you reflected whatever they are being applied to?
Why can't they accept lambda expressions, functions, or delegates? Aren't functions constant to the compiler?

Attributes could be a phenomenally powerful declarative tool if just one of the above were true, instead they are more like comments that can be read through reflection.

This was sort of a rant, but I really want to know why they seem like such a half assed feature.

Here is what I wanted to do. It was supposed to be an api for mapping values from resources through the function given to the attribute to the property the attribute is applied to. Note that the abstract class wouldn't have to exist if Attributes could know what they are reflecting. I'm posting this because someone wanted to know why I would want to give functions to attribute constructors, and maybe because what I'm trying to do has already been done.

public delegate void PropertyHandler(object parent, PropertyInfo property, object value);

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class FromResourceAttribute : Attribute
{
    private static readonly PropertyHandler m_defaultHandler =  (parent, property, value) => 
                                                                {
                                                                    property.SetValue(parent, value, null);
                                                                };

    public PropertyHandler Handler { get; set; }

    public FromResourceAttribute(PropertyHandler handler)
    {
        Handler = handler;
    }

    public FromResourceAttribute()
    {
        Handler = m_defaultHandler;
    }
}

public abstract class ResourceDependent
{
    public ResourceDependent(ResourceManager resources)
    {
        var resourceDependentProperties = 
            from property in GetType().GetProperties()
            let fromResourceAttributes = property.GetCustomAttributes(typeof(FromResourceAttribute), true)
            where fromResourceAttributes.Count() == 1
            let propertyHandler = ((FromResourceAttribute)fromResourceAttributes.Single()).Handler
            select new { Info = property, Handler = propertyHandler };

        foreach(var property in resourceDependentProperties)
        {
            property.Handler(this, property.Info, resources.GetObject(property.Info.Name));
        }
    }
}

class ResourceDependentTest : ResourceDependent
{
    [FromResource]
    public string Data { get; set; }

    [FromResource((parent, property, value) => property.SetValue(parent, ((string)value).Split('|'), null))]
    public string[] Data2 { get; set; }

    static PropertyHandler Data3Handler =   (parent, property, value) =>
                                            {
                                                //Magic
                                            };

    [FromResource(Data3Handler)]
    public int Data3 { get; set; }

    public ResourceDependentTest() : base(Properties.Resources.ResourceManager)
    {

    }
}
+8  A: 

Why can't they have information you would get if you reflected whatever they are being applied to?

Part of the reason is attributes don't have to be applied to anything. It's perfectly legal to new up an attribute in imperative code which is not attached to anything.

// Not attached
var attrib = new CLSCompliantAttribute(false);

Why can't they accept lambda expressions, functions, or delegates? Aren't functions constant to the compiler?

All of these resolve down to the question of why delegates cannot be a part of an attribute. At their core delegates are made up of two parts 1) an instance and 2) a pointer to a method. #1 pretty much kills it as the instance is not constant and can't be a part of the attribute value.

There are several other case which exist with delegates including delegate's to static methods and expression trees. These have similar issues though in that all parts of the expression are not constant hence not encodable in an attribute value.

JaredPar
A lambda expression is not necessarily a delegate, though - it can also represent an expression tree, which could, ostensibly, be encoded in an attribute.
Pavel Minaev
@Pavel true it could be an expression tree. But I don't see how that could be encoded into an attribute.
JaredPar
Same way as any literal is encoded: by specifying an encoding of an expression tree into a sequence of bytes, and storing that in assembly metadata. It is fairly obvious that any expression tree that does not reference "live" objects (i.e. that for which `ConstantExpression` nodes contain values representable as literals in IL) can be represented that way.
Pavel Minaev
@Pavel, I see what you're saying. There exist a subset of expression trees which have the capability of being expressed as constant values. Yes I agree those could exist as constants.
JaredPar
@JaredPar - doubt you can skip Attribute suffix in plain code - just nitpicking :P
DK
@DK, good point, updated
JaredPar
+4  A: 

Actually the question about why they can't be generic has been answered before, and according to Eric Lippert they are not supported simply because they'd add complexity to the language and it's just not been considered worth doing so far. It's certainly not because it's compile time. In IL you apparently can make generic attributes. (Reference.)

As for why they can't take lambda expressions and so forth, I imagine it's the same reason. The request for delegates to be used has been Microsoft Connect since 2006. You might vote for it if you like.

Richard Hein
Lambdas and generics wrt attributes are different. Generics are supported in IL level but not by C# compiler. Lambdas are not supported on either level - reason being that there is no "lambda literals". Decimal (`1.0m`) and VB `DateTime` (`#01/01/2010#`) are not supported in attributes for the same reason.
Pavel Minaev
Thanks for the additional info.
Richard Hein
+1  A: 

Im not very into attributes (because in my opinion they are made for tool builders like nunit), but i think you already answered your question: Attributes are compile time constant

Attribute parameters are restricted to constant values of the following types:

* Simple types (bool, byte, char, short, int, long, float, and double)
* string
* System.Type
* enums
* object (The argument to an attribute parameter of type object

must be a constant value of one of the above types.) * One-dimensional arrays of any of the above types

http://msdn.microsoft.com/en-us/library/aa288454%28VS.71%29.aspx#vcwlkattributestutorialanchor1

while the returned value of lambdas, delegates and functions are not.

I dont really want to know how code with heavy lambda use in attributes would look like, should be quite a mess.

atamanroman
You can find out in my edited post.
cory
+1  A: 

The short, short, version, is that IL doesn't support more functionality; see ECMA-335 Partition II §21 Custom attributes and Partition II §23.3 Custom attributes.

To summarize §23.3, custom attributes use a custom serialization format that is stored within IL, not Binary Serialization, not XML or SOAP serialization. Quoting from the standard §23.3:

The FieldOrPropType shall be exactly one of: ELEMENT_TYPE_BOOLEAN, ELEMENT_TYPE_CHAR, ELEMENT_TYPE_I1, ELEMENT_TYPE_U1, ELEMENT_TYPE_I2, ELEMENT_TYPE_U2, ELEMENT_TYPE_I4, ELEMENT_TYPE_U4, ELEMENT_TYPE_I8, ELEMENT_TYPE_U8, ELEMENT_TYPE_R4, ELEMENT_TYPE_R8, ELEMENT_TYPE_STRING. A single-dimensional, zero-based array is specified as a single byte 0x1D followed by the FieldOrPropType of the element type. (See §23.1.16) An enum is specified as a single byte 0x55 followed by a SerString.followed by a SerString.

The obvious response, of course, is "why doesn't the IL serialization format support method references/etc. And the obvious answer, of course, is that they felt it wasn't necessary and/or it would further bloat IL.

(An interesting aside: Type references are actually implemented by using a string containing the fully-qualified assembly name of the type. By the same logic, I suppose a method reference could be "encoded" by using the type name and an encoding for the method, but I suspect this would be "brittle", in that adding/changing methods could break things.)

jonp