如何缩短属性,重复获取和设置器



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

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

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

此属性和其中的代码可以在一个类中多次出现,也可以在整个项目的serval类中出现,唯一更改的是属性的名称和它本身的支持字段,以及方法调用中的参数。

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

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

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

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

看看福迪。
他们有大量的插件,其中一些做着非常相似的事情。如果你找不到你喜欢的那个,你可以修改它来做你的意愿(并同时把它发回去为社区做出贡献)。

例如,Fody的 PropertyChanged 插件将更改以下 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);
        }
    }
}

总结

使用拦截来解决属性实现方式中的横切问题。

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


解释和示例

我的理解是,您主要关注的是面向方面的编程练习。您希望将类的定义与持久化基础数据的方式以及任何后续后果(例如引发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的动态代理。 实际的逻辑并不重要,或者可能与您的需求相关 - 但它至少应该给出如何在特定情况下实现您想要的东西的概念视图。

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

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

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

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

然后对于您的定义...好吧,假设这是人.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使t4工具箱使大型,复杂的T4项目变得更容易,但我警告你:T4是一条通往疯狂的道路。

二传手可以用单行代码替换:

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

您可以执行类似操作:

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委托传递给wrapper

您可以使用

PostSharp。

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

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

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

  1. 创建自定义属性并应用于这些字段
  2. 检测何时应用
  3. 该属性:创建一个始终运行的另一个应用,并检查您应用了该属性的时间。我会尝试罗斯林CTP
  4. 使用分部类。将属性生成到另一个文件。

最新更新