How do you give a C# Auto-Property a default value using a custom attribute?
Asked Answered
P

4

2

How do you give a C# Auto-Property a default value, using a custom attribute?

This is the code I want to see:

class Person 
{
    [MyDefault("William")]
    public string Name { get; set; }
}

I am aware that there is no built in method to initialize the default using an attribute - can I write my own custom class that uses my custom attributes to initialize the default?

Protege answered 20/6, 2011 at 0:2 Comment(2)
Why don't you just use the constructor?Kedgeree
It is not so interesting probably :)Cath
W
4

You could use a helper class like that:

public class DefaultValueHelper
{
    public static void InitializeDefaultValues<T>(T obj)
    {
        var properties =
            (from prop in obj.GetType().GetProperties()
             let attr = GetDefaultValueAttribute(prop)
             where attr != null
             select new
             {
                Property = prop,
                DefaultValue = attr.Value
             }).ToArray();
        foreach (var p in properties)
        {
            p.Property.SetValue(obj, p.DefaultValue, null);
        }

    }

    private static DefaultValueAttribute GetDefaultValueAttribute(PropertyInfo prop)
    {
        return prop.GetCustomAttributes(typeof(DefaultValueAttribute), true)
                   .Cast<DefaultValueAttribute>()
                   .FirstOrDefault();
    }
}

And call InitializeDefaultValues in the constructor of your class.

class Foo
{
    public Foo()
    {
        DefaultValueHelper.InitializeDefaultValues(this);
    }

    [DefaultValue("(no name)")]
    public string Name { get; set; }
}

EDIT: updated version, which generates and caches a delegate to do the initialization. This is to avoid using reflection every time the method is called for a given type.

public static class DefaultValueHelper
{
    private static readonly Dictionary<Type, Action<object>> _initializerCache;

    static DefaultValueHelper()
    {
        _initializerCache = new Dictionary<Type, Action<object>>();
    }

    public static void InitializeDefaultValues(object obj)
    {
        if (obj == null)
            return;

        var type = obj.GetType();
        Action<object> initializer;
        if (!_initializerCache.TryGetValue(type, out initializer))
        {
            initializer = MakeInitializer(type);
            _initializerCache[type] = initializer;
        }
        initializer(obj);
    }

    private static Action<object> MakeInitializer(Type type)
    {
        var arg = Expression.Parameter(typeof(object), "arg");
        var variable = Expression.Variable(type, "x");
        var cast = Expression.Assign(variable, Expression.Convert(arg, type));
        var assignments =
            from prop in type.GetProperties()
            let attr = GetDefaultValueAttribute(prop)
            where attr != null
            select Expression.Assign(Expression.Property(variable, prop), Expression.Constant(attr.Value));

        var body = Expression.Block(
            new ParameterExpression[] { variable },
            new Expression[] { cast }.Concat(assignments));
        var expr = Expression.Lambda<Action<object>>(body, arg);
        return expr.Compile();
    }

    private static DefaultValueAttribute GetDefaultValueAttribute(PropertyInfo prop)
    {
        return prop.GetCustomAttributes(typeof(DefaultValueAttribute), true)
                   .Cast<DefaultValueAttribute>()
                   .FirstOrDefault();
    }
}
Weak answered 20/6, 2011 at 0:28 Comment(9)
Using reflection is expensiveQuezada
@DustinDavis, yes, but it doesn't mean you should never use it... Of course this is not a good solution if you must create many instances of the class.Weak
Of course, an option would be to generate and cache a delegate that initializes the properties. That way the reflection would be done only once.Weak
Of course. I use it often, but i assume he is going to want this functionality on a big scale. It would be nice if there were some attribute framework that we could work with.Quezada
Put up an example of the delegate cache and i'll give you +1Quezada
I've already posted such example yet before you started conversation :)Cath
@Thomas Just for getting property list/attributes. CLR caches it itself, so there's not much overhead. Most slowness lies in the setter call itself: CLR should check type safety, arguments, code access permissions etc. All of that I perform just one time. But if it is really critical part - you could of course cache list of properties & values for each type. It is simple but rather long... If it is really necessary for someone - I will show it also. Otherwise... I'm somewhat lazy :)Cath
@DustinDavis, I just posted a new version. Had to drop the generic signature though...Weak
@Thomas I've done it also. Btw my version will work on .NET 3.5 also)))) But +1 is yours)Cath
Q
6

If you want to do it with PostSharp (as your tags suggest) then use a Lazy Loading aspect. You can see the one I built here http://programmersunlimited.wordpress.com/2011/03/23/postsharp-weaving-community-vs-professional-reasons-to-get-a-professional-license/

With an aspect you can apply default value to a single property or apply it to multiple properties with a single declaration at the class level.

Lazy loading aspect will use LocationInterceptionAspect base class.

[Serializable]
    [LazyLoadingAspect(AttributeExclude=true)]
    [MulticastAttributeUsage(MulticastTargets.Property)]
    public class LazyLoadingAspectAttribute : LocationInterceptionAspect
    {
        public object DefaultValue {get; set;}

        public override void OnGetValue(LocationInterceptionArgs args)
        {
           args.ProceedGetValue();
            if (args.Value != null)
            {
              return;
            }

            args.Value = DefaultValue;
            args.ProceedSetValue();
        }

    }

then apply the aspect like so

[LazyLoadingAspect(DefaultValue="SomeValue")]
public string MyProp { get; set; }
Quezada answered 20/6, 2011 at 0:28 Comment(0)
W
4

You could use a helper class like that:

public class DefaultValueHelper
{
    public static void InitializeDefaultValues<T>(T obj)
    {
        var properties =
            (from prop in obj.GetType().GetProperties()
             let attr = GetDefaultValueAttribute(prop)
             where attr != null
             select new
             {
                Property = prop,
                DefaultValue = attr.Value
             }).ToArray();
        foreach (var p in properties)
        {
            p.Property.SetValue(obj, p.DefaultValue, null);
        }

    }

    private static DefaultValueAttribute GetDefaultValueAttribute(PropertyInfo prop)
    {
        return prop.GetCustomAttributes(typeof(DefaultValueAttribute), true)
                   .Cast<DefaultValueAttribute>()
                   .FirstOrDefault();
    }
}

And call InitializeDefaultValues in the constructor of your class.

class Foo
{
    public Foo()
    {
        DefaultValueHelper.InitializeDefaultValues(this);
    }

    [DefaultValue("(no name)")]
    public string Name { get; set; }
}

EDIT: updated version, which generates and caches a delegate to do the initialization. This is to avoid using reflection every time the method is called for a given type.

public static class DefaultValueHelper
{
    private static readonly Dictionary<Type, Action<object>> _initializerCache;

    static DefaultValueHelper()
    {
        _initializerCache = new Dictionary<Type, Action<object>>();
    }

    public static void InitializeDefaultValues(object obj)
    {
        if (obj == null)
            return;

        var type = obj.GetType();
        Action<object> initializer;
        if (!_initializerCache.TryGetValue(type, out initializer))
        {
            initializer = MakeInitializer(type);
            _initializerCache[type] = initializer;
        }
        initializer(obj);
    }

    private static Action<object> MakeInitializer(Type type)
    {
        var arg = Expression.Parameter(typeof(object), "arg");
        var variable = Expression.Variable(type, "x");
        var cast = Expression.Assign(variable, Expression.Convert(arg, type));
        var assignments =
            from prop in type.GetProperties()
            let attr = GetDefaultValueAttribute(prop)
            where attr != null
            select Expression.Assign(Expression.Property(variable, prop), Expression.Constant(attr.Value));

        var body = Expression.Block(
            new ParameterExpression[] { variable },
            new Expression[] { cast }.Concat(assignments));
        var expr = Expression.Lambda<Action<object>>(body, arg);
        return expr.Compile();
    }

    private static DefaultValueAttribute GetDefaultValueAttribute(PropertyInfo prop)
    {
        return prop.GetCustomAttributes(typeof(DefaultValueAttribute), true)
                   .Cast<DefaultValueAttribute>()
                   .FirstOrDefault();
    }
}
Weak answered 20/6, 2011 at 0:28 Comment(9)
Using reflection is expensiveQuezada
@DustinDavis, yes, but it doesn't mean you should never use it... Of course this is not a good solution if you must create many instances of the class.Weak
Of course, an option would be to generate and cache a delegate that initializes the properties. That way the reflection would be done only once.Weak
Of course. I use it often, but i assume he is going to want this functionality on a big scale. It would be nice if there were some attribute framework that we could work with.Quezada
Put up an example of the delegate cache and i'll give you +1Quezada
I've already posted such example yet before you started conversation :)Cath
@Thomas Just for getting property list/attributes. CLR caches it itself, so there's not much overhead. Most slowness lies in the setter call itself: CLR should check type safety, arguments, code access permissions etc. All of that I perform just one time. But if it is really critical part - you could of course cache list of properties & values for each type. It is simple but rather long... If it is really necessary for someone - I will show it also. Otherwise... I'm somewhat lazy :)Cath
@DustinDavis, I just posted a new version. Had to drop the generic signature though...Weak
@Thomas I've done it also. Btw my version will work on .NET 3.5 also)))) But +1 is yours)Cath
C
4

If to speculate with Expressions you could make initializing delegates and cache them. It will make code much faster comparing with just pure reflection.

internal static class Initializer
{
    private class InitCacheEntry
    {
        private Action<object, object>[] _setters;
        private object[] _values;

        public InitCacheEntry(IEnumerable<Action<object, object>> setters, IEnumerable<object> values)
        {
            _setters = setters.ToArray();
            _values = values.ToArray();

            if (_setters.Length != _values.Length)
                throw new ArgumentException();
        }

        public void Init(object obj)
        {
            for (int i = 0; i < _setters.Length; i++)
            {
                _setters[i](obj, _values[i]);
            }
        }
    }

    private static Dictionary<Type, InitCacheEntry> _cache = new Dictionary<Type, InitCacheEntry>();

    private static InitCacheEntry MakeCacheEntry(Type targetType)
    {
        var setters = new List<Action<object, object>>();
        var values = new List<object>();
        foreach (var propertyInfo in targetType.GetProperties())
        {
            var attr = (DefaultAttribute) propertyInfo.GetCustomAttributes(typeof (DefaultAttribute), true).FirstOrDefault();
            if (attr == null) continue;
            var setter = propertyInfo.GetSetMethod();
            if (setter == null) continue;

            // we have to create expression like (target, value) => ((TObj)target).setter((T)value)
            // where T is the type of property and obj is instance being initialized
            var targetParam = Expression.Parameter(typeof (object), "target");
            var valueParam = Expression.Parameter(typeof (object), "value");
            var expr = Expression.Lambda<Action<object, object>>(
                Expression.Call(Expression.Convert(targetParam, targetType),
                                setter,
                                Expression.Convert(valueParam, propertyInfo.PropertyType)),
                targetParam, valueParam);
            var set = expr.Compile();

            setters.Add(set);
            values.Add(attr.DefaultValue);
        }
        return new InitCacheEntry(setters, values);
    }

    public static void Init(object obj)
    {
        Type targetType = obj.GetType();
        InitCacheEntry init;
        if (!_cache.TryGetValue(targetType, out init))
        {
            init = MakeCacheEntry(targetType);
            _cache[targetType] = init;
        }
        init.Init(obj);
    }
}
Cath answered 20/6, 2011 at 0:39 Comment(0)
K
1

You could create a method like this:

public static void FillProperties<T>(T obj)
{
    foreach (var property in typeof(T).GetProperties())
    {
        var attribute = property
            .GetCustomAttributes(typeof(DefaultValueAttribute), true)
            .Cast<DefaultValueAttribute>()
            .SingleOrDefault();
        if (attribute != null)
            property.SetValue(obj, attribute.Value, null);
    }
}

You can then either use a factory method that calls this method or call it directly from the constructor. Note that this usage of reflection is probably not a good idea if you create a lot of objects this way and performance is important.

Kedgeree answered 20/6, 2011 at 0:25 Comment(2)
Thank you for the answer. To call this, call "FillProperties(this)" from your class's constructor (assuming that the class is non static).Protege
This works 100%. I also set up a new attribute "MyDefault", and used this attribute to set the default value of each Property. This way, if the code is ported to a different project, it will break noisily rather than silently.Protege

© 2022 - 2024 — McMap. All rights reserved.