如何缩短属性,重复get和setter

时间:2013-10-30 08:17:51

标签: c# .net properties attributes

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

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

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

这个例子会使用一个属性,但也许有更好的东西,或者属性是最好的方法。我该如何实现这样的属性?

8 个答案:

答案 0 :(得分:5)

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

查看Fody 他们有很多插件,其中一些做类似的事情。如果你找不到你喜欢的那个,你可以修改它来做你的意愿(并将其发回以同时为社区做贡献)。

Fody的{​​{3}}插件会更改这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);
        }
    }
}

答案 1 :(得分:3)

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

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的定义作为读者的excersize。当然,这是一种格外粗糙的方法 - 使用t4构建框架的结构和智能方法要多得多。

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

Oleg Sych让t4toolbox更容易制作大型复杂的T4项目,但我警告你:T4是疯狂之路。

答案 2 :(得分:3)

Setter可以替换为单行:

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

e.g:

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

答案 3 :(得分:3)

你可以做类似的事情:

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委托传递给包装器

答案 4 :(得分:3)

您可以使用PostSharp

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

答案 5 :(得分:3)

<强>摘要

使用拦截来解决有关如何实施属性的交叉问题。

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


解释和示例

我的理解是你基本上关注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容器配置级别配置拦截。

答案 6 :(得分:2)

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

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

答案 7 :(得分:2)

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