开发者

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

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?


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; }


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();
    }
}


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);
    }
}


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜