views:

324

answers:

3

EDIT: I'd better rephrase: How can I shift the GET-implementation of a Class property to a / using a custom attribute? (I've added instantation vars (classname, propertyname) to the attribute, however I'd rather have these automatically fetched ofcourse.)

Public Class CustomClass
    <CustomAttributeClass(ClassName:="CustomClass", PropertyName = "SomeProperty")> _
    Public Property SomeProperty() as String
        Get() as String
            //This implementation should be handled by the attribute class
        End Get

        Set(Byval value as String)
            Me._someProperty = value
        End Set
    End Property
End Class

Old question:

I want to create a custom property attribute for classes. I can create a class derived from Attribute, and 'mark' the property with the attribute, but where to go from here?

I have a repository where I can quickly get data based on the attributes values. I would like to generalize the behaviour of the property in the attribute but I don't know how to go from here... Any help would be greatly accepted!

Public Class CustomDataAttribute : Inherits Attribute
    Private _name As String

    Public Sub New(ByVal name As String)
        Me.Name = name
    End Sub

    Property Name() As String
        Get
            Return _name
        End Get
        Set(ByVal value As String)
            Me._name = value
        End Set
    End Property
End Class


Public Class CustomClass
    <CustomDataAttribute(Name:="CustomField")> _ 
    Public Property CustomField()
    End Property
End Class
+2  A: 

You will have to use Reflection to discover the attribute. In your case, you'll get it from PropertyInfo.GetCustomAttributes().

The harder part of using attributes is to find a suitable execution model to actually use them. Something like a compiler, a designer or a class that serializes objects is an obvious one. The usability of attributes quickly spirals down from there. It is almost always the wrong choice when you try to use an attribute where a virtual property is actually needed. Retrieving attribute values is very expensive, many orders of magnitude more expensive than retrieving a property value. Use them only when the reflection code runs at human time (like compilers) or when the cost is insignificant compared to the benefit or the overhead (common in any kind of I/O operation).

Hans Passant
Can you please elaborate: I want to apply localization to properties custom business objects. Is the overriding / attributing of properties a very bad way to go? If it were only one property per object, I would create an interface to define the property-to-localize. However multiple properties per object need to be localized. Any suggestions?
Ropstah
"very" expensive is not quite right. Technically more expensive than a method call, yes. But attribute-oriented models are used throughout the .NET world in systems that are *extremely* sensitive to performance. Using attributes in your system will not be the bottleneck.
Rex M
But I have no idea on where to go with the reflector. It seems odd to do looking at what I'm trying to accomplish...... Isn't there some base property-attribute which has some events like onGet and onSet?
Ropstah
@ropstah: I'm having trouble understanding your use-case. Localization is a UI detail, it shouldn't be applicable to business objects. Localization is already well supported in .NET UI frameworks like WF, WPF and ASP.NET. Look there first.
Hans Passant
I don't understand how I can use the default resources provider (which I implemented as a SQL Resource Provider) for localized content in business classes (these localized resources need to be used in more places than just views). Or should all resources which aren't specific to a single view be Global-resources?
Ropstah
+1  A: 

From your comments to the previous answer, I think you'll find that .NET attributes aren't quite as flexible as you want them to be.

You asked "Isn't there some base property-attribute which has some events like onGet and onSet?" -- no; attributes have no built-in interaction with their targets, whether methods or classes. In fact, you can't even tell at runtime what the target of an attribute is; you have to know the target (class, method, property etc) first and then query which attributes decorate it.

Secondly, attributes aren't actually created until you query for them. When you call GetCustomAttributes, the runtime system evaluates the assembly metadata and instantiates the attributes that were specified. If you call it twice in a row, you'll get two sets of the same attributes.

Going back to your other question: if you want to know when a property decorated with your attributes is set or retrieved, you'd have to implement INotifyPropertyChanged on all of your related classes, write code to search all your classes for properties marked with that attribute upon assembly load, and then build some interactivity that hooked up the PropertyChanged events to whatever code you need to fire. (And that only notifies you about set operations, not get.)

No idea if this is helpful, but there you go. :-)

Ben M
some very helpful side information for somebody who hasn't done more with attributes than a custom authorize attribute. However I still have no idea on where to go from here... I just need to change some already existing business classes to have some properties to be localized... I cannot use the default Resources provider because that stores a path of the view to make the resources local. However I need to localize (as to local resources, not localization! lol) the resources based on the business classes, not based views...
Ropstah
A: 

Here is a class which helps processing custom attributes when using reflection. Pass the type as a parameter to contructor.

public class AttributeList : List<Attribute>
{
    /// <summary>
    /// Gets a list of custom attributes
    /// </summary>
    /// <param name="propertyInfo"></param>
    /// <returns></returns>
    public static AttributeList GetCustomAttributeList(ICustomAttributeProvider propertyInfo)
    {
        var result = new AttributeList();
        result.AddRange(propertyInfo.GetCustomAttributes(false).Cast<Attribute>());
        return result;
    }

    /// <summary>
    /// Finds attribute in collection by its own type or parents type
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public T FindAttribute<T>() where T : Attribute
    {
        return (T)Find(x => typeof(T).IsAssignableFrom(x.GetType()));
    }

    /// <summary>
    /// Finds attribute in collection by its own type or parents type
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public List<T> FindAllAttributes<T>() where T : Attribute
    {
        return new List<T>(FindAll(x => typeof(T).IsAssignableFrom(x.GetType())).Cast<T>());
    }

    /// <summary>
    /// Finds attribute in collection by its own type or parents type
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public List<T> FindAllAttributes<T>(Type implementsType) where T : Attribute
    {
        return new List<T>(FindAll(x => implementsType.IsAssignableFrom(x.GetType())).Cast<T>());
    }

    public bool IsAttributeSet<T>() where T : Attribute
    {
        return FindAttribute<T>() != null;
    }

    public TValue GetValueFromAttributeOrDefault<TAttr, TValue>(Func<TAttr, TValue> func, TValue defaultValue) 
        where TAttr : Attribute
    {
        var attribute = FindAttribute<TAttr>();
        return attribute == null ? 
            defaultValue : 
            func(attribute);
    }
}

Maksym Kozlenko