根据类型和参数列表删除ifs
本文关键字:列表 删除 ifs 参数 类型 | 更新日期: 2023-09-27 17:51:13
我想重构以下递归方法:
public static void Initialize(Control control, DocumentContainer container, ErrorProvider provider)
{
if (control == null)
{
return;
}
var controlWithTextBase = control as ICustomControlWithText;
if (controlWithTextBase != null)
{
controlWithTextBase.DocumentLoaded = true;
controlWithTextBase.Initialize(container, provider);
}
var custom = control as CustomCheckbox;
if (custom != null)
{
custom.DocumentLoaded = true;
custom.Initialize(container);
}
foreach (Control subControl in control.Controls)
{
Initialize(subControl, container, provider);
}
}
public interface ICustomControlWithText : ICustomControl
{
void Initialize(DocumentContainer container, ErrorProvider provider);
void InitializeValidations();
string Text { get; set; }
ErrorProvider ErrorProvider { get; set; }
List<IValidation> Validations { get; set; }
}
public interface ICustomControl
{
void Clear();
FieldType FieldType { get; set; }
bool DocumentLoaded { get; set; }
}
class CustomCheckbox : CheckBox, ICustomControl
{
public void Initialize(DocumentContainer container)
{
//...
}
}
可以看到,根据winforms控件的类型,这段代码初始化了一个控件。它从主表单开始,它包含自定义控件(IControlWithText,CustomCheckbox)和默认的winforms表单。我会创建3个初始化器,并根据控件的类型对每个方法进行初始化,但即使这样,我也不知道如何跳过那些"如果",我需要知道是否需要将此ErrorProvider发送到方法Initialize。
我将非常感谢你的帮助!您可以使用"动态过载解析"。(需要.Net 4+)
如果你将输入控件转换为dynamic
, . net将在运行时寻找合适的重载。
小心地为意外类型的控制提供"catch"重载。这就是object
过载的好处。否则,您可能会遇到运行时异常。
public static void Initialize(Control control, DocumentContainer container, ErrorProvider provider)
{
if (control == null) return;
dynamic c = control;
InitializeControl(c, container, provider);
foreach (Control subControl in control.Controls)
Initialize(subControl, container, provider);
}
public static void InitializeControl(ICustomControlWithText controlWithTextBase, DocumentContainer container, ErrorProvider provider)
{
controlWithTextBase.DocumentLoaded = true;
controlWithTextBase.Initialize(container, provider);
}
public static void InitializeControl(CustomCheckbox custom, DocumentContainer container, ErrorProvider provider)
{
custom.DocumentLoaded = true;
custom.Initialize(container);
}
public static void InitializeControl(object _, DocumentContainer container, ErrorProvider provider)
{
// do nothing if the control is neither a ICustomControlWithText nor a CustomCheckbox
}
您要查找的是访客(Gang of Four)模式。
通过添加额外的Accept
方法来确保您的基本接口ICustomControl
接受访问者。让来访者命名为ControlVisitor
,但其他名字也可以。
public interface ICustomControl
{
void Accept(ControlVisitor visitor);
void Clear();
FieldType FieldType { get; set; }
bool DocumentLoaded { get; set; }
}
简化Initialize
方法
public static void Initialize(Control control, ControlVisitor visitor)
{
if (control == null) //can this ever be null?
{
return;
}
var customControl = control as ICustomControl;
if (customControl != null)
{
customControl.Accept(visitor);
}
foreach (Control subControl in control.Controls)
{
Initialize(subControl, visitor);
}
}
并通过添加访问者来填补空白(我的ControlVisitor
示例是一个具体的,但您也可以为它提供接口)。这里您将提供一个重载的Visit
方法
public class ControlVisitor
{
private readonly DocumentContainer container;
private readonly ErrorProvider provider;
public ControlVisitor(DocumentContainer container, ErrorProvider provider)
{
this.container = container;
this.provider = provider;
}
public void Visit(ICustomControlWithText control)
{
control.DocumentLoaded = true;
control.Initialize(container, provider);
}
public void Visit(CustomCheckbox control)
{
control.DocumentLoaded = true;
control.Initialize(container);
}
}
Accept方法的实现非常简单,无论在哪里执行都是一样的。下面是CustomCheckBox
的示例:
public class CustomCheckbox : CheckBox, ICustomControl
{
//..
public void Accept(ControlVisitor visitor)
{
visitor.Visit(this);
}
//..
}
我同意@3dGrabber的观点,但这里有一个不需要动态的解决方案,但与3dGrabber的答案非常相似。但严格适用于。net的旧版本。。
public class ClassInitiator{
public static void Initialize(Control control,
DocumentContainer container, ErrorProvider provider)
{
if (control == null) return;
typeof(ClassInitiator).InvokeMember(
"InitializeControl",
BindingFlags.InvokeMethod | BindingFlags.Public,
null,
null,
new object[]{
control,
container,
provider
});
foreach (Control subControl in control.Controls)
Initialize(subControl, container, provider);
}
public static void InitializeControl(
ICustomControlWithText controlWithTextBase,
DocumentContainer container,
ErrorProvider provider)
{
controlWithTextBase.DocumentLoaded = true;
controlWithTextBase.Initialize(container, provider);
}
public static void InitializeControl(
CustomCheckbox custom,
DocumentContainer container,
ErrorProvider provider)
{
custom.DocumentLoaded = true;
custom.Initialize(container);
}
public static void InitializeControl(
object _,
DocumentContainer container,
ErrorProvider provider)
{
// do nothing if the control is neither a
// ICustomControlWithText nor a CustomCheckbox
}
}
注意,这只是为了支持以前的。net 2.0版本,该版本不支持动态,从性能上讲,动态更快,因为它缓存了调用方法所需的运行时解析,但在这种情况下,InvokeMember的解析需要更长的时间。
另一种方法是解析Method并缓存它,如下所示。
private Dictionary<Type,MethodInfo> cache =
new Dictionary<Type,MethodInfo>();
public static void Initialize(Control control,
DocumentContainer container, ErrorProvider provider)
{
if (control == null) return;
MethodInfo initializer = null;
Type controlType = control.GetType();
if(!cache.TryGetValue(controlType, out initializer)){
initializer = typeof(ClassInitialor).GetMethod("InitializeControl",
new Type[] {
controlType,
typeof(DocumentContainer),
typeof(ErrorProvider),
});
cache[controlType] = initializer;
}
initializer.Invoke(null,
new object[] {
control,
container,
provider
});
foreach (Control subControl in control.Controls)
Initialize(subControl, container, provider);
}
从第二个接口删除初始化逻辑,只留下验证逻辑,因此您可以编写如下内容:
public interface ICustomControl
{
void Clear();
FieldType FieldType { get; set; }
bool DocumentLoaded { get; set; }
void Initialize(DocumentContainer container);
}
public interface ICustomControlWithText
{
string Text { get; set; }
ErrorProvider ErrorProvider { get; set; }
List<IValidation> Validations { get; set; }
void Validate(ErrorProvider provider);
}
class CustomCheckbox : CheckBox, ICustomControl { ... }
class CustomTextbox : TextBox, ICustomControl, ICustomControlWithText {...}
public static void Initialize(Control control, DocumentContainer container, ErrorProvider provider)
{
var custom = control as ICustomControl;
if (control == null)
return;
custom.DocumentLoaded = true;
custom.Initialize(container);
var controlWithTextBase = control as ICustomControlWithText;
if (controlWithTextBase != null)
controlWithTextBase.Validate(provider);
foreach (Control subControl in control.Controls)
Initialize(subControl, container, provider);
}
当然你也可以让ICustomControlWithText "inherit" ICustomControl
public interface ICustomControlWithText : ICustomControl
可能是基于多态性的东西。我认为这很简单:
class TempTest
{
public static void Run()
{
IData data = new InitData() { IntegerData = 1, StringData = "some" };
IBaseControl c1 = new ControlA();
IBaseControl c2 = new ControlB();
c1.Init( data );
c2.Init( data );
}
}
// Interfaces
public interface IData
{
int IntegerData { get; set; }
string StringData { get; set; }
}
public interface IBaseControl
{
void Init( IData data );
}
public interface IControlA
{
void Init( int IntegerData );
}
public interface IControlB
{
void Init( int IntegerData, string StringData );
}
// Base classes
public abstract class Base : IBaseControl
{
#region IBaseControl Members
public abstract void Init( IData data );
#endregion
}
// Concrete classes
public class InitData : IData
{
public int IntegerData { get; set; }
public string StringData { get; set; }
}
public class ControlA : Base, IControlA
{
public override void Init( IData data )
{
Init( data.IntegerData );
}
#region IControlA Members
public void Init( int IntegerData )
{
Console.WriteLine( "ControlA initialized with IntegerData={0}", IntegerData );
}
#endregion
}
public class ControlB : Base, IControlB
{
public override void Init( IData data )
{
Init( data.IntegerData, data.StringData );
}
#region IControlB Members
public void Init( int IntegerData, string StringData )
{
Console.WriteLine( "ControlB initialized with IntegerData={0} and StringData={1}", IntegerData, StringData );
}
#endregion
}
我想你明白了。
我会这样做:
-
将
void Initialize(DocumentContainer container, ErrorProvider provider);
方法从ICustomControlWithText
移动到ICustomControl
-
从
更改CustomCheckbox中的
Initialize
方法签名public void Initialize(DocumentContainer)
public void Initialize(DocumentContainer container, ErrorProvider provider);
是的,provider
变量永远不会被CustomCheckBox使用,但那又怎样?在执行时间和代码大小方面,为方法调用添加一个额外的参数比使用访问者或动态方法(IMHO)要便宜得多
-
按如下方式重写递归方法:
public static void Initialize(Control control, DocumentContainer container, ErrorProvider provider) { if (control == null) { return; } var custom = control as ICustomControl; if (custom != null) { custom.DocumentLoaded = true; custom.Initialize(container, provider); } foreach (Control subControl in control.Controls) { Initialize(subControl, container, provider); } }
我一直使用这个模式:
首先,定义一个初始化接口。此接口将用于定义如何初始化控件类型。
public interface IInitializer
{
void Intialize(Control c, DocumentContainer container, ErrorProvider provider);
bool Accept(Control c);
}
第二,创建一些初始化式,每个初始化式代表一个if语句。
public class InitializerForControlWithTextBase : IInitializer
{
public void Intialize(Control control, DocumentContainer container, ErrorProvider provider)
{
var c = control as ICustomControlWithText;
c.DocumentLoaded = true;
c.Initialize(container, provider);
}
public bool Accept(Control c)
{
return GetBaseType().IsInstanceOfType(c);
}
public Type GetBaseType()
{
return typeof(ICustomControlWithText);
}
}
public class InitializerForCustomCheckbox : IInitializer
{
public void Intialize(Control control, DocumentContainer container, ErrorProvider provider)
{
var c = control as CustomCheckbox;
c.DocumentLoaded = true;
c.Initialize(container);
}
public bool Accept(Control c)
{
return GetBaseType().IsInstanceOfType(c);
}
public Type GetBaseType()
{
return typeof(CustomCheckbox);
}
}
第三,重写Initialize.
public static void Initialize(IEnumerable<IInitializer> initializers, Control control, DocumentContainer container, ErrorProvider provider)
{
if (control == null) return;
var inSituInitializer = control as IInitializer;
if (inSituInitializer != null)
{
inSituInitializer.Intialize(control, container, provider);
}
else
{
foreach (var initializer in initializers)
{
if (initializer.Accept(control))
{
initializer.Intialize(control, container, provider);
break;
}
}
}
foreach (Control subControl in control.Controls)
{
Initialize(initializers, subControl, container, provider);
}
}
您将注意到,我首先检查了控件是否已经实现了初始化器。如果是,那么您可以直接进行初始化,而无需进一步搜索(很容易将其放置在您自己的代码中)。如果使用第三方控件,您可以找到并使用上面步骤2中的一个初始化式。
最后,在某处调用初始化式。
var inits = new IInitializer[]
{
new InitializerForControlWithTextBase(),
new InitializerForCustomCheckbox(),
};
Initialize(inits, control, container, provider);
这种方法的好处是:
你有两种方法来找到一个初始化器——把它放在你的控件上,或者循环并找到一个第三方控件。(当然,如果你有数百个初始化式,你可以使用字典进一步提高循环性能)。
代码是解耦的-您可以将单个初始化器放在单独的DLL中。