根据类型和参数列表删除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控件的类型,这段代码初始化了一个控件。它从主表单开始,它包含自定义控件(IControlWithTextCustomCheckbox)和默认的winforms表单。我会创建3个初始化器,并根据控件的类型对每个方法进行初始化,但即使这样,我也不知道如何跳过那些"如果",我需要知道是否需要将此ErrorProvider发送到方法Initialize。

我将非常感谢你的帮助!

根据类型和参数列表删除ifs

您可以使用"动态过载解析"。(需要.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
    }

我想你明白了。

我会这样做:

  1. void Initialize(DocumentContainer container, ErrorProvider provider);方法从ICustomControlWithText移动到ICustomControl

  2. 更改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);
    

    这种方法的好处是:

    1. 你有两种方法来找到一个初始化器——把它放在你的控件上,或者循环并找到一个第三方控件。(当然,如果你有数百个初始化式,你可以使用字典进一步提高循环性能)。

    2. 代码是解耦的-您可以将单个初始化器放在单独的DLL中。