优化大型交换机语句



我有一个大型switch语句,其中我根据XElement:的输入值创建UIElements

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

我正在创建很多这样的控件,正如你所看到的,太多的重复正在进行

有没有办法避免这种重复?提前感谢您的想法。

您可以创建一个通用函数来创建:

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.

这样的东西可以工作…:)

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;

这些不同的控件都有继承树。例如,宽度、高度和名称都是在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;

请注意,通过使用这种方法,与Flynn的答案相比,您还可以轻松地添加诸如"当控件是ItemsControl时,请执行此操作"之类的代码,即不适用于所有类型,而仅适用于其中一些类型的代码。

您可以使用反射+表达式。

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

要使其适用于其他类型,然后是字符串,可以使用Expression.Convert.Left作为练习。

您可以使用反射来实现这一点,或者您可以在创建控件的地方创建一个字符串字典(您现在打开的内容)和Funcs字典(或者操作)。

对于您发布的特定代码,您可以在switch语句之后指定高度和宽度,因为它们直接存在于Control上。

最新更新