Оптимизация большого оператора переключения - PullRequest
5 голосов
/ 23 мая 2011

У меня есть большой оператор switch, в котором я создаю UIElements на основе входного значения из XElement:

public static UIElement CreateElement(XElement element) {
            var name = element.Attribute("Name").Value;
            var text = element.Attribute("Value").Value;
            var width = Convert.ToDouble(element.Attribute("Width").Value);
            var height = Convert.ToDouble(element.Attribute("Height").Value);
            //...
            switch (element.Attribute("Type").Value) {
                case "System.Windows.Forms.Label":
                    return new System.Windows.Controls.Label() {
                        Name = name,
                        Content = text,
                        Width = width,
                        Height = height
                    };
                case "System.Windows.Forms.Button":
                    return new System.Windows.Controls.Button() {
                        Name = name,
                        Content = text,
                        Width = width,
                        Height = height
                    };
                    //...
                default:
                    return null;
            }
        }

Я создаю много элементов управления, подобных этому, и, как вы видите, слишком много повторений.

Есть ли способ избежать этого повторения? Заранее спасибо за идеи.

Ответы [ 5 ]

6 голосов
/ 23 мая 2011

Как-то так может работать ...:)

var controlCreators = new Dictionary<string, Func<ContentControl>>
                        {
                            {"System.Windows.Forms.Label", () => new Label()},
                            {"System.Windows.Forms.Button", () => new Button()}
                        };

Func<ContentControl> createControl;
if (!controlCreators.TryGetValue(element.Attribute("Type").Value, out createControl))
{
    return null;
}

var control = createControl();
control.Name = name;
control.Content = text;
control.Width = width;
control.Height = height;
return control;
6 голосов
/ 23 мая 2011

Вы можете создать универсальную функцию для создания:

private static Create<T>(string name, string text, double width, double height) where T: Control, new()
{
   return new T { Name = name, Content = text, Width = width, Height = height }
}

Ваш переключатель становится:

switch (element.Attribute("Type").Value) {
  case "System.Windows.Forms.Label" : return Create<System.Windows.Forms.Label>(name, text, width, height);
  etc.
}

Вы также можете адаптировать это для передачи в XElement, в зависимости от того, что вы предпочитаете.

Если атрибут Type - это всегда имя System.Type, которое вы хотите, тогда вы можете просто сделать

Control ctrl = (Control) Activator.CreateInstance(Type.GetType(element.Attribute("Type").Value));
ctrl.Name = name;
etc.

Если между значением атрибута и желаемым типом есть сопоставление один к одному, вы можете объявить статическое поле только для чтения с отображением:

private static readonly uiTypeMapping = new Dictionary<string,Type> {
  { "System.Windows.Forms.Label", typeof(System.Windows.Controls.Label) },
  { "System.Windows.Forms.Button", typeof(System.Windows.Controls.Button) },
  { etc. }
};

И используйте

UIElement elem = (UIElement) Activator.CreateInstance(uiTypeMapping[element.Attribute("Type").Value]);
etc.
1 голос
/ 23 мая 2011

Вы можете сделать это с отражением + выражениями.

[TestClass]
public class UnitTest1
{
    public class Creator
    {
        private static Dictionary<string,Func<XElement, Control>> _map = new Dictionary<string, Func<XElement,Control>>();

        public static Control Create(XElement element)
        {
            var create = GetCreator(element.Attribute("Type").Value);

            return create(element);
        }

        private static Expression<Func<XElement, string>> CreateXmlAttributeAccessor(string elementName)
        {
            return (xl => xl.Attributes(elementName).Select(el => el.Value).FirstOrDefault() ?? "_" + elementName);
        }

        private static Func<XElement, Control> GetCreator(string typeName)
        {
            Func<XElement, Control> existing;
            if (_map.TryGetValue(typeName, out existing))
                return existing;

            // mapping for whatever property names you wish
            var propMapping = new[]
            {
                new{ Name = "Name", Getter = CreateXmlAttributeAccessor("Name") },
                new{ Name = "Content", Getter = CreateXmlAttributeAccessor("Value") },
            };

            var t = Assembly.GetAssembly(typeof (Control)).GetType("System.Windows.Controls." + typeName);

            var elementParameter = Expression.Parameter(typeof (XElement), "element");

            var p = from propItem in propMapping
                    let member = t.GetMember(propItem.Name)
                    where member.Length != 0
                    select (MemberBinding)Expression.Bind(member[0], Expression.Invoke(propItem.Getter, elementParameter));

            var expression = Expression.Lambda<Func<XElement, Control>>(
                Expression.MemberInit(Expression.New(t),p), elementParameter);

            existing = expression.Compile();
            _map[typeName] = existing;

            return existing;
        }
    }

    [TestMethod]
    public void TestMethod1()
    {
        var xel = new XElement("control",
            new XAttribute("Type", "Button"),
            new XAttribute("Name", "Foo"),
            new XAttribute("Value", "Bar"),
            new XElement("NonExistent", "foobar")); // To check stability

        var button = (Button) Creator.Create(xel);

        Assert.AreEqual("Foo", button.Name);
        Assert.AreEqual("Bar", button.Content);
    }
}

Чтобы заставить его работать с другими типами, кроме string, вы можете использовать Expression.Convert.Оставлено как упражнение.

1 голос
/ 23 мая 2011

Эти разные элементы управления имеют деревья наследования.Так, например, ширина, высота, имя определены на FrameworkElement.Таким образом, вы могли бы сделать что-то вроде следующего:

object createdObject = null;
switch (element.Attribute("Type").Value)
{
case "System.Windows.Forms.Label":
    createdObject = new System.Windows.Controls.Label();
    break;
case "System.Windows.Forms.Button":
    createdObject = new System.Windows.Controls.Button();
    break;
}

var fe = createdObject as FrameworkElement;
if (fe != null)
{
    fe.Name = element.Attribute("Name").Value;
    fe.Width = Convert.ToDouble(element.Attribute("Width").Value);
    fe.Height = Convert.ToDouble(element.Attribute("Height").Value);
}

var ce = createdObject as ContentElement;
if (ce != null)
{
     ce.Content = element.Attribute("Value").Value;
}

return createdObject;

Обратите внимание, что используя этот подход, по сравнению с ответом Флинна, вы также можете легко добавить код, такой как «когда элемент управления является ItemsControl, сделайте это»то есть код, который не будет применяться к каждому типу, но только к некоторым из них.

0 голосов
/ 23 мая 2011

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

для конкретногоРазместив код, вы можете назначить высоту и ширину после оператора switch, поскольку они существуют непосредственно в Control.

...