4

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

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

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

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

4

8 回答 8

5

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

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

例如,PropertyChanged插件Fody将更改这 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);
        }
    }
}
于 2013-11-11T13:05:49.100 回答
3

You can do something similar to this:

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
}

I don't know a lot about the details of your task but this will give you the basic idea. In case your classes don't share the same parent, this will prevent code duplication. In case if they do, you can hide this wrapper in base class and don't pass OnPropetyChanged delegate to wrapper

于 2013-11-08T20:57:12.050 回答
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

人.模板.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 是一条通往疯狂的道路。

于 2013-11-06T18:43:02.503 回答
3

设置器可以用单行替换:

    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 );
        }
    }
于 2013-11-07T13:31:36.507 回答
3

您可以使用PostSharp

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

于 2013-11-11T08:59:30.137 回答
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();
        }
    }
}

我严重依赖拦截来解决横切问题。我在接口的实现中使用了 Castle 的动态代理。IInterceptor实际的逻辑并不重要,或者可能与您的需求相关 - 但它至少应该提供一个概念性的观点,说明如何在特定情况下实现您想要的。

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

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

于 2013-11-11T22:38:33.607 回答
2

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

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

于 2013-10-30T08:38:33.687 回答
2
  1. 创建自定义属性并应用于这些字段
  2. 检测您何时应用该属性:创建另一个始终运行的应用程序,并检查您是否应用了该属性。我会试试Roslyn CTP
  3. 使用部分类。将属性生成到另一个文件。
于 2013-11-11T11:28:12.267 回答