我在重复代码时遇到问题,想知道一种进一步缩短代码的方法。
这就是我的代码目前的样子:
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 片段(如果您不是)。这不会减少代码量,但会大大减少编写此类属性所需的时间。
- 创建自定义属性并应用于这些字段 检测何时应用
- 该属性:创建一个始终运行的另一个应用,并检查您应用了该属性的时间。我会尝试罗斯林CTP
- 使用分部类。将属性生成到另一个文件。