Удаление ifs на основе типа и списка параметров

Я хочу реорганизовать следующий рекурсивный метод:

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 Инициализатора и каждый метод CanInitialize в зависимости от типа элемента управления, но даже тогда я понятия не имею, как пропустить те "ifs", которые мне нужно знать, если мне нужно отправить этот ErrorProvider в метод Инициализировать.

Буду очень признателен вам за помощь!

Ответ 1

Вы можете использовать "Динамическое разрешение перегрузки". (Требуется .Net 4+)

Если вы нажмете свой входной элемент управления на dynamic,.Net будет искать подходящую перегрузку во время выполнения.
Будьте осторожны, чтобы обеспечить "перехват" перегрузки для случая непредвиденного типа управления. Это то, что перегрузка 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
}

Ответ 2

Что вы ищете, это шаблон Visitor (Gang of Four).

Убедитесь, что ваш базовый интерфейс ICustomControl принимает посетителей, добавив к нему дополнительный метод Accept. Пусть этот посетитель будет называться 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);
        }
        //..
    }

Ответ 3

Я согласен с @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 занимает больше времени.

Альтернативой будет разрешение метода и кэширование его, как показано ниже.

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

Ответ 4

Удалите логику инициализации со второго интерфейса и оставите только логику проверки, чтобы вы могли написать что-то вроде этого:

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 "наследовать" ICustomControl, если хотите

public interface ICustomControlWithText : ICustomControl

Ответ 5

Возможно, что-то основано на полиморфизме. Это просто и довольно просто:

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
    }

Я думаю, вы поняли.

Ответ 6

Я бы сделал следующее:

  • Переместите метод void Initialize(DocumentContainer container, ErrorProvider provider); с ICustomControlWithText на ICustomControl

  • Измените Initialize подпись метода в CustomCheckbox

public void Инициализировать (контейнер DocumentContainer)

к

public void Инициализировать (контейнер DocumentContainer, поставщик ErrorProvider);

Да, переменная provider никогда не будет использоваться CustomCheckBox, но что? Это намного дешевле с точки зрения времени выполнения и размера кода, чтобы добавить один дополнительный параметр в вызов метода, чем использовать посетителей или динамические методы (IMHO)

  1. Перепишите рекурсивный метод следующим образом:

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

Ответ 7

Я использую этот шаблон все время:

Сначала определите интерфейс инициализатора. Этот интерфейс будет использоваться для определения того, как инициализируются типы элементов управления.

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

В-третьих, перепишите инициализацию.

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

Вы заметите, что я сначала проверил, проверяет ли уже реализованный элемент IIntializer. Если это так, то вы можете выполнить инициализацию напрямую без дальнейшего поиска (легко разместить это на своем собственном коде). Если вы используете сторонние элементы управления, вы находите и используете один из инициализаторов с шага 2 выше.

Наконец, вызовите инициализатор где-нибудь.

            var inits = new IInitializer[] 
            { 
                new InitializerForControlWithTextBase(), 
                new InitializerForCustomCheckbox(), 
            };

            Initialize(inits, control, container, provider);

Преимущество такого подхода:

  • У вас есть два способа найти иналиализатор - поместите его под свой контроль или выполните цикл и найдите его для стороннего элемента управления. (Вы можете, конечно же, улучшить производительность в цикле со словарем, если у вас есть сотни инициализаторов).

  • Код развязан - вы можете поместить отдельные инициализаторы в отдельную DLL.