Lambda expression in attribute constructor

23,973

Solution 1

You cannot

  • you cannot create generic attribute types (it simply isn't allowed); equally, no syntax for using generic attributes ([Foo<SomeType>]) is defined
  • you cannot use lambdas in attribute initializers - the values available to pass to attributes is very limited, and simply does not include expressions (which are very complex, and are runtime objects, not compile-time literals)

Solution 2

Having a generic attribute is not possible in a conventional way. However C# and VB don't support it but the CLR does. If you want to write some IL code it's possible.

Let's take your code:

[AttributeUsage(AttributeTargets.Property)]
public class RelatedPropertyAttribute: Attribute
{
    public string RelatedProperty { get; private set; }

    public RelatedPropertyAttribute(string relatedProperty)
    {
       RelatedProperty = relatedProperty;
    }
}

Compile the code, open up the assembly with ILSpy or ILDasm and then dump the content to a text file. The IL of you attribute class declaration will look like this:

.class public auto ansi beforefieldinit RelatedPropertyAttribute
extends [mscorlib]System.Attribute

In the text file, you can then make the attribute generic. There are several things that need to be changed.

This can simply be done by changing the IL and the CLR won't complain:

.class public abstract auto ansi beforefieldinit
      RelatedPropertyAttribute`1<class T>
      extends [mscorlib]System.Attribute

and now you can change the type of relatedProperty from string to your generic type.

For Example:

.method public hidebysig specialname rtspecialname 
    instance void .ctor (
        string relatedProperty
    ) cil managed

change it to:

.method public hidebysig specialname rtspecialname 
    instance void .ctor (
        !T relatedProperty
    ) cil managed

There are lot of frameworks to do a "dirty" job like that: Mono.Cecil or CCI.

As I have already said it's not a clean object oriented solution but just wanted to point out another way to break the limit of C# and VB.

There's an interesting reading around this topic, check it out this book.

Hope it helps.

Solution 3

If you are using C# 6.0, you can use nameof

Used to obtain the simple (unqualified) string name of a variable, type, or member. When reporting errors in code, hooking up model-view-controller (MVC) links, firing property changed events, etc., you often want to capture the string name of a method. Using nameof helps keep your code valid when renaming definitions. Before you had to use string literals to refer to definitions, which is brittle when renaming code elements because tools do not know to check these string literals.

with it you can use your attribute like this:

public class MyClass
{
    public int EmployeeID { get; set; }

    [RelatedProperty(nameof(EmployeeID))]
    public int EmployeeNumber { get; set; }
}

Solution 4

One of possible workarounds is to define class for each property relationship and to reference it by
typeof() operator in attribute constructor.

Updated:

For example:

[AttributeUsage(AttributeTargets.Property)]
public class RelatedPropertyAttribute : Attribute
{
    public Type RelatedProperty { get; private set; }

    public RelatedPropertyAttribute(Type relatedProperty)
    {
        RelatedProperty = relatedProperty;
    }
}

public class PropertyRelation<TOwner, TProperty>
{
    private readonly Func<TOwner, TProperty> _propGetter;

    public PropertyRelation(Func<TOwner, TProperty> propGetter)
    {
        _propGetter = propGetter;
    }

    public TProperty GetProperty(TOwner owner)
    {
        return _propGetter(owner);
    }
}

public class MyClass
{
    public int EmployeeId { get; set; }

    [RelatedProperty(typeof(EmployeeIdRelation))]
    public int EmployeeNumber { get; set; }

    public class EmployeeIdRelation : PropertyRelation<MyClass, int>
    {
        public EmployeeIdRelation()
            : base(@class => @class.EmployeeId)
        {

        }
    }
}

Solution 5

You can't. Attribute types are limited as written here. My suggestion, try to evaluate your lambda expression externally, then use one 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
Share:
23,973
Dave New
Author by

Dave New

Updated on December 27, 2020

Comments

  • Dave New
    Dave New over 3 years

    I have created an Attribute class called RelatedPropertyAttribute:

    [AttributeUsage(AttributeTargets.Property)]
    public class RelatedPropertyAttribute: Attribute
    {
        public string RelatedProperty { get; private set; }
    
        public RelatedPropertyAttribute(string relatedProperty)
        {
            RelatedProperty = relatedProperty;
        }
    }
    

    I use this to indicate related properties in a class. Example of how I would use it:

    public class MyClass
    {
        public int EmployeeID { get; set; }
    
        [RelatedProperty("EmployeeID")]
        public int EmployeeNumber { get; set; }
    }
    

    I would like to use lambda expressions so that I can pass a strong type into my attribute's constructor, and not a "magic string". This way I can exploit compiler type checking. For example:

    public class MyClass
    {
        public int EmployeeID { get; set; }
    
        [RelatedProperty(x => x.EmployeeID)]
        public int EmployeeNumber { get; set; }
    }
    

    I thought I could do it with the following, but it isn't allowed by the compiler:

    public RelatedPropertyAttribute<TProperty>(Expression<Func<MyClass, TProperty>> propertyExpression)
    { ... }
    

    Error:

    The non-generic type 'RelatedPropertyAttribute' cannot be used with type arguments

    How can I achieve this?

  • Ilya Ivanov
    Ilya Ivanov almost 11 years
    Mark, by the way, do you know why we can't pass Decimals as parameters into Attributes? I've never encountered an explanation behind this limitation. edit: Found at use decimal values as attribute params in c#?
  • Marc Gravell
    Marc Gravell almost 11 years
    @Ilya because IL doesn't know about decimals, even if C# does. There is simply no direct IL mechanism for representing them in the metadata.
  • George Duckett
    George Duckett almost 11 years
    @IlyaIvanov: Really this is a separate question, but luckly it's already been asked: stackoverflow.com/questions/3192833/…
  • WPF-it
    WPF-it over 9 years
    Code that really makes things easy to follow.
  • codingadventures
    codingadventures about 9 years
    @MatthiasMüller Thanks for the upvote! Sorry for asking but are you the same guy who release the paper Real time dynamic fracture with VACD?
  • Matthias Müller
    Matthias Müller about 9 years
    Hi, no I'm just a junier developer and I love such mad approaches :D
  • Andrey Burykin
    Andrey Burykin almost 8 years
    ...base(@class => @class.EmployeeId)... ?
  • julealgon
    julealgon over 7 years
    Very interesting approach. Maybe you could expand on the code sample by adding bits on how to use the GetProperty method though.
  • Roman Starkov
    Roman Starkov about 6 years
    There's a feature request on GitHub to support lambdas in attributes in C#.