【问题标题】:How to shorten a property, with repeating get and setter如何通过重复 get 和 setter 来缩短属性
【发布时间】:2013-11-09 17:14:21
【问题描述】:

我有重复代码的问题,想知道一种进一步缩短代码的方法。

这就是我的代码目前的样子:

private string _description = null;    
public string Description
{
    get
    {
        _description = GetLang(this.TabAccountLangs, "TextAccount");
        return _description;
    }
    set
    {
        if (object.Equals(value, _description))
            return;

        SetLang(this.TabAccountLangs, "TextAccount", value);

        OnPropertyChanged();
    }
}

这个属性和其中的代码可以在一个类和整个项目的多个类中多次出现,唯一改变的是属性的名称和它自身的支持字段,以及来自方法调用的参数.

现在我想知道,是否有办法进一步缩短此代码,例如: (只是伪代码)

[DoYourSuff(FirstParam=this.TabAccountLangs, SecondParam="TextAccount", ThirdParam=value)]
public string Description { get; set; }

此示例将使用属性,但也许有更好的方法,或者该属性是执行此操作的最佳方式。我将如何实现这样的属性?

【问题讨论】:

  • 奇怪的是你只在get中给_description赋值。
  • 你或许可以研究一下PropertyBag的用法。
  • @Blam:我基本上只是在重新设置相同值时使用支持字段来抑制多个 OnPropertyChanged() 调用,你将如何解决这个问题?
  • 我的 cmets 解释了它。
  • @Blam: ?!?抱歉没明白你的意思,能不能详细的给我解释一下?或者提供一些代码,你将如何解决这个问题,没有支持字段。

标签: c# .net properties attributes


【解决方案1】:

如果不构建一些框架,您将无法实现此类属性,该框架将通过您的解决方案并在幕后为此类属性生成代码。创建,更重要的是,调试这样的东西需要付出很多努力,而且通常是不值得的。至少当唯一的原因是“缩短代码”时不会。

相反,我建议尽可能使用继承和聚合。您还应该考虑制作一个 Resharper 模板(如果您使用的是 Resharper)或 VS sn-ps(如果您不是)。这不会减少代码量,但会大大减少编写此类属性所需的时间。

【讨论】:

  • 回答得很好。我想添加一个无法使用此类属性的原因:this.TabAccountLangsvalue 等变量值不能在属性中使用。我能想象的唯一可能性是将硬编码的字符串传递给属性,然后使用反射调用正确的方法。但这必须在 get 和 set 中完成,并且会破坏此类属性的目的。
【解决方案2】:

如果你真的想走这条路?代码生成将起作用。

http://msdn.microsoft.com/en-us/library/vstudio/bb126445.aspx

Microsoft 已将 T4 模板语言嵌入到 Visual Studio 中。这种模板语言允许快速简便地生成样板代码。虽然系统本身很原始、笨拙且通常令人沮丧,但它允许您使用任何您喜欢的方法生成代码。

要完成基本操作,您需要创建一个模板文件来描述您的可重用代码和逻辑。

例如,我们可以有一个如下所示的 TemplatedFields.Include.tt 文件

<# // myFields and myClassName must be defined before importing this template #>
<# // stuff in these braces will not appear in the outputted file, but are executed by the templating engine #>
//this code is outside of the braces and will appear in the file1
public partial class <#= myClassName #> //notice the equals sign.  Works like webforms.
{

    <# ForEach(var field in myFields) { #>
    private string _<#= field.Name #> = null;    
    public string <#= CapitalizeFirstLetter(field.Name) #>
    {
        get
        {
            _<#= field.Name #> = GetLang(<#= field.FirstParam #>, "<#= field.SecondParam #>");
            return _<#= field.Name #>;
        }
        set
        {
            if (object.Equals(value, _<#= field.Name #>))
                return;

            SetLang(<#= field.FirstParam  #>, "<#= field.SecondParam #>", value);

            OnPropertyChanged();
        }
    }
    <# } #>
}

然后对于你的定义......好吧,假设这是 Person.cs

Person.Templated.tt

<#@ output extension=".cs" #>
//stuff inside the angle braces is sent to the TT engine and does not appear in the file.
<#
var myClassName = "Person";
var myFields = new List<Field>()
{
    new Field {Name="Description", FirstParam="this.TabAccountLangs", SecondParam="TextAccount"),
    new Field {Name="Name", FirstParam="this.TabAccountLangs", SecondParam="TextAccount"),
    new Field {Name="MoarFieldzzzz", FirstParam="this.TabAccountLangs", SecondParam="TextAccount"),
}
 #>
 //included code is appears below, now that values have been set above.
 <#@ include file="TemplatedFields.Include.tt" #>

保存上述文件会自动生成 Person.Templated.cs。我不记得你是否需要一个指令来确保 VS 会编译生成的 CS 文件,但我很确定它默认会编译。

我将 CapitalizeFirstLetter 的实现和 Field 的定义留给读者阅读。当然,这是一种非常粗略的方法 - 使用 t4 构建框架的方式要结构化和智能得多。

由于类是部分的,您可以在第二个 Person.cs 文件中提供更具体的手工编码逻辑。

Oleg Sych 使 t4toolbox 使大型、复杂的 T4 项目变得更容易,但我警告你:T4 是一条通往疯狂的道路。

【讨论】:

    【解决方案3】:

    Setter 可以用单行替换:

        private string foo;
        public string Foo
        {
            get { return foo; }
            set { Setter(v => foo = v, value, () => Foo, () => Bar); }
        }
    

    例如:

     set { Setter( v => SetLang(this.TabAccountLangs, "TextAccount", v), value, () => Foo );
    

    “Setter”是基类中的方法:

    public abstract class BaseObject : INotifyPropertyChanged
        {
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected void Setter<T>( Action<T> setter, T newVal, Expression<Func<T>> property, params Expression<Func<object>>[] dependentProperties )
            {
            if ( !equals( getPropValue( property ), newVal ) )
                {
                setter( newVal );
                notifyDependentProps( property, dependentProperties );
                }
            }
    
        private static string getPropertyName<Tz>( Expression<Func<Tz>> property )
            {
            return getPropertyInfo( property ).Name;
            }
    
        private static PropertyInfo getPropertyInfo<T>( Expression<Func<T>> property )
            {
            MemberExpression expression;
            var body = property.Body as UnaryExpression;
            if ( body != null )
                expression = (MemberExpression) body.Operand; //for value types
            else
                expression = ( (MemberExpression) property.Body );
            var pi = expression.Member as PropertyInfo;
            if ( pi == null )
                throw new ArgumentException( "expression must be valid property" );
            return pi;
            }
    
        private void valueChanged<Ta>( Expression<Func<Ta>> property )
            {
            if ( PropertyChanged != null )
                PropertyChanged( this, new PropertyChangedEventArgs( getPropertyName( property ) ) );
            }
    
        private void notifyDependentProps<T>( Expression<Func<T>> property, Expression<Func<object>>[] dependentProps )
            {
            valueChanged( property );
            if ( dependentProps != null && dependentProps.Length > 0 )
                {
                for ( int index = 0; index < dependentProps.Length; index++ )
                    valueChanged( dependentProps[index] );
                }
            }
    
        private T getPropValue<T>( Expression<Func<T>> property )
            {
            PropertyInfo pi = getPropertyInfo( property );
            return (T) pi.GetValue( this, new object[] {} );
            }
    
        private bool equals<T>( T first, T second )
            {
            return EqualityComparer<T>.Default.Equals( first, second );
            }
        }
    

    【讨论】:

      【解决方案4】:

      你可以做类似这样的事情:

      public class MyClass
      {
          private TabAccountLangs TabAccountLangs = //whatever;
          private readonly Wrapper _wrapper = new Wrapper(TabAccountLangs);
      
          private string Decsription
          {
              get { return _wrapper.GetValue<string>("TextAccount"); }
              set { _wrapper.SetValue<string>("TextAccount", value, OnPropertyChanged); }
          }
      }
      
      public class Wrapper
      {
          private Dictionary<string, object> _map = new Dictionary<string, object>(); 
      
          //pass TabAccountLangs to constructor and assign to _langs property
          //Constructor should be here
      
          public T GetValue<T>(string name)
          {
              object result;
              if (!_map.TryGetValue(name, out result))
              {
                  result = GetLang(_langs, name);
                  _map[name] = result;
              }
              return (T) result;
          }
      
          public void SetValue<T>(string name, T value, Action onSuccess)
          {
              object previousValue;
              if (_map.TryGetValue(name, out previousValue) && previousValue.Equals(value))
              {
                  return;
              }
              SetLang(_langs, name);
              _map[name] = value;
              onSuccess();
          }
      
          //The rest
      }
      

      我对你的任务的细节了解不多,但这会给你基本的想法。如果您的类不共享同一个父类,这将防止代码重复。如果他们这样做了,您可以在基类中隐藏此包装器,并且不要将 OnPropetyChanged 委托传递给包装器

      【讨论】:

        【解决方案5】:

        您可以使用PostSharp

        我不会在这里粘贴任何示例:他们的网站上有很多!

        【讨论】:

          【解决方案6】:
          1. 创建自定义属性并应用于这些字段
          2. 检测何时应用该属性:创建另一个始终运行的应用程序,并检查您是否应用了该属性。我会试试Roslyn CTP
          3. 使用部分类。将属性生成到另一个文件。

          【讨论】:

            【解决方案7】:

            几个答案似乎值得,但这里有另一种选择。

            看看Fody
            他们有大量的插件,其中一些做的事情非常相似。如果你找不到你喜欢的,你可以根据自己的意愿对其进行修改(同时将其发回为社区做出贡献)。

            例如,FodyPropertyChanged 插件将更改这 51 行代码:

            public class Person : INotifyPropertyChanged
            {
                public event PropertyChangedEventHandler PropertyChanged;
            
                string givenNames;
                public string GivenNames
                {
                    get { return givenNames; }
                    set
                    {
                        if (value != givenNames)
                        {
                            givenNames = value;
                            OnPropertyChanged("GivenNames");
                            OnPropertyChanged("FullName");
                        }
                    }
                }
            
                string familyName;
                public string FamilyName
                {
                    get { return familyName; }
                    set 
                    {
                        if (value != familyName)
                        {
                            familyName = value;
                            OnPropertyChanged("FamilyName");
                            OnPropertyChanged("FullName");
                        }
                    }
                }
            
                public string FullName
                {
                    get
                    {
                        return string.Format("{0} {1}", GivenNames, FamilyName);
                    }
                }
            
                public virtual void OnPropertyChanged(string propertyName)
                {
                    var propertyChanged = PropertyChanged;
                    if (propertyChanged != null)
                    {
                        propertyChanged(this, new PropertyChangedEventArgs(propertyName));
                    }
                }
            }
            

            到 14 点:

            [ImplementPropertyChanged]
            public class Person 
            {        
                public string GivenNames { get; set; }
                public string FamilyName { get; set; }
            
                public string FullName
                {
                    get
                    {
                        return string.Format("{0} {1}", GivenNames, FamilyName);
                    }
                }
            }
            

            【讨论】:

              【解决方案8】:

              总结

              使用拦截来解决您的属性实现方式中的横切关注点。

              属性可用于将静态元数据关联到您的代码,而运行时依赖项需要更多配置。


              说明和示例

              我的理解是,您主要关心Aspect Orientated Programming 中的一个练习。您希望将类的定义与基础数据的持久化方式以及任何后续后果(例如引发INotifyPropertyChanged 事件)解耦。

              您的案例的有趣之处在于您希望同时使用静态数据(代码示例中的字符串值"TextAccount")和仅在运行时才知道的数据(代码示例中的this.TabAccountLangs)。这些类型的数据需要不同的方法。

              在我的解决方案中发生了很多事情,但让我先发布代码然后解释一下:

              internal class Program
              {
                  private static void Main(string[] args)
                  {
                      var cOldClass = new PlainOldClass();
                      var classProxyWithTarget = 
                          new ProxyGenerator().CreateClassProxyWithTarget(cOldClass,new Intercetor(cOldClass));
                      classProxyWithTarget.PropertyOne = 42;
                      classProxyWithTarget.PropertyTwo = "my string";
                  }
              
              }
              
              [AttributeUsage(AttributeTargets.Property)]
              public class StaticDataAttribute : Attribute
              {
                  public string StaticData { get; private set; }
              
                  public StaticDataAttribute(string resourceKey)
                  {
                      StaticData = resourceKey;
                  }
              }
              
              public interface IMyRuntimeData
              {
                  string TabAccountLangs { get; }
                  void OnPropertyChanged(string propertyName = null);
              }
              
              public class PlainOldClass : IMyRuntimeData
              {
                  [StaticData("FirstProperty")]
                  public virtual int PropertyOne { get; set; }
              
                  public string PropertyTwo { get; set; }
                  public event PropertyChangedEventHandler PropertyChanged;
              
                  public string TabAccountLangs { get; private set; }
              
                  public virtual void OnPropertyChanged(string propertyName = null)
                  {
                      var handler = PropertyChanged;
                      if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
                  }
              }
              
              public class Intercetor: IInterceptor
              {
                  private readonly IMyRuntimeData _runtimeData;
              
                  public Intercetor(IMyRuntimeData runtimeData)
                  {
                      _runtimeData = runtimeData;
              
                  }
              
                  public void Intercept(IInvocation invocation)
                  {
                      var isPropertySetter = invocation.Method.Name.StartsWith("set_");
                      var isPropertyGetter = invocation.Method.Name.StartsWith("get_");
              
                      if (isPropertySetter)
                      {
                          //Pre Set Logic
                          invocation.Proceed();
                          //Post Set Logic
                          var attribute = invocation.Method.GetCustomAttributes(false).Cast<StaticDataAttribute>().SingleOrDefault();
                          if (attribute != null)
                          {
                              string resourceKey = attribute.StaticData;
                              string tabAccountLangs = _runtimeData.TabAccountLangs;
                              _runtimeData.OnPropertyChanged(invocation.Method.Name.Substring(4));
                          }   
                      } else if (isPropertyGetter)
                      {
                          //Pre Get Logic 
                          invocation.Proceed();
                          //Post Get Logic
                      }
                      else
                      {
                          invocation.Proceed();
                      }
                  }
              }
              

              我非常依赖拦截来解决跨领域问题。我在实现IInterceptor 接口时使用了Castle 的Dynamic Proxy。实际的逻辑并不重要,或者可能与您的需求相关 - 但它至少应该提供一个概念性视图,说明如何在特定情况下实现您想要的。

              我关心的属性被标记为virtual(因此动态代理会拦截对它们的调用),并用StaticDataAttribute 装饰,以便我将静态数据附加到每个属性。更棘手的部分是依赖于直到运行时才确定的数据的拦截的那些方面,即引发属性更改事件,并使用this.TabAccountLangs 的值。这些“运行时依赖”被封装在接口IRuntimeData中,被注入到拦截器的构造函数中。

              Main(string[] args) 方法显示了所有内容是如何组合在一起的。显然你不会在代码中这样使用它——你可以将这种“粘合”逻辑包装在工厂模式中,或者在 IoC 容器配置级别配置拦截。

              【讨论】:

                猜你喜欢
                • 2020-01-04
                • 2019-11-04
                • 2020-01-04
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2015-04-16
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多