2011-06-20 274 views
2

如何使用自定义属性为C#自动属性设置默认值?如何使用自定义属性为C#自动属性设置默认值?

这是我希望看到的代码:

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

据我所知,没有内置的方法使用属性初始化默认 - 我可以写我自己的自定义类,使用我的自定义属性初始化默认?

+6

为什么你不使用构造函数? – svick

+1

这可能不是很有趣:) –

回答

4

你可以使用一个辅助类这样的:

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

而且在类的构造函数中调用InitializeDefaultValues

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

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

编辑:更新后的版本,生成和缓存的委托做初始化。这是为了避免每次调用给定类型的方法时使用反射。

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

使用反射是昂贵的 –

+0

@DustinDavis,是的,但这并不意味着你不应该使用它......当然,如果你必须创建许多类的实例,这不是一个好的解决方案。 –

+0

当然,一个选项是生成并缓存初始化属性的委托。这样反射只会完成一次。 –

1

您可以创建这样一个方法:

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

然后,您可以使用调用此方法的工厂方法或直接从构造函数中调用它。请注意,如果以这种方式创建大量对象并且性能很重要,则反射的这种用法可能不是一个好主意。

+0

谢谢你的回答。要调用它,从你的类的构造函数中调用“FillProperties(this)”(假设这个类是非静态的)。 – Contango

+0

这项工作100%。我还设置了一个新属性“MyDefault”,并使用此属性来设置每个属性的默认值。这样,如果代码移植到不同的项目中,它将会以噪音而不是默默的方式打破。 – Contango

6

如果你想与PostSharp做(因为你的标签提示),然后使用一个懒加载方面。你可以看到我在这里建立的那个http://programmersunlimited.wordpress.com/2011/03/23/postsharp-weaving-community-vs-professional-reasons-to-get-a-professional-license/

通过一个方面,你可以将默认值应用于单个属性,或者将其应用于多个属性并在类级别使用一个声明。

延迟加载方面将使用LocationInterceptionAspect基类。

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

    } 

然后应用方面,像这样

[LazyLoadingAspect(DefaultValue="SomeValue")] 
public string MyProp { get; set; } 
+4

+1,因为你给他一个postharp答案 –

4

如果使用表达式来推测你可以做初始化代表和缓存。与纯粹的反射相比,它将使代码更快。

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); 
    } 
} 
相关问题