我遇到重复代码的问题,想知道进一步缩短代码的方法。
这就是我的代码目前的样子:
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; }
这个例子会使用一个属性,但也许有更好的东西,或者属性是最好的方法。我该如何实现这样的属性?
答案 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)