我可以创建一个在第一个参数中有所不同的Func字典吗?

时间:2017-11-20 19:25:57

标签: c# .net

我有一堆静态方法,除了第一个参数的类型外,都有相同的签名,例如:

public static class ElementCreators
{
    public static XmlElement CreateElementForString
               (string s, string name, string namespaceURI)
    {
        [...]
    }

    public static XmlElement CreateElementForDecimal
               (Decimal d, string name, string namespaceURI)
    {
        [...]
    }
}

我想创建一个Dictionary(或某种可以在运行时修改的查找 - 人们应该能够添加自己的func,但是一旦添加了func,它就不需要修改/删除,并且给定类型永远不会有两个+ func。可能有基类和派生类型的函数,但在这种情况下,由用户注册它们,例如,按照正确的顺序)根据类型进行调度,例如:

var funcs = new Dictionary<Type, Func<object, string, string, XmlElement>>();
funcs[typeof(string)] = ElementCreators.CreateElementForString;
funcs[typeof(Decimal)] = ElementCreators.CreateElementForDecimal;

现在,这不起作用,因为委托之间没有矛盾,所以编译器抱怨CS0123 No overload for 'CreateElementForString' matches delegate 'Func<object, string, string, XmlElement>'

一种选择是创建另一个委托作为中间人:

funcs[typeof(string)] =
            (o,s1,s2) => ElementCreators.CreateElementForString((string)o, s1, s2);

这有效,但是a)丑陋,b)介绍了一堆不必要的代表。

泛型似乎不是一个选项,因为Func不能是开放类型T.同样,动态不起作用,但我不想使用这些(运行时成本)。

我可以为每个方法引入一个间接级别,这可以避免委托,但它不会那么难看:

public static XmlElement CreateElementForString(object s, string name, string namespaceURI)
    => CreateElementForString((string)s, name, namespaceURI);

当然,我可以尝试自动化类似的东西(T4模板,预构建任务,自定义构建操作等)。

但在我这样做之前,我想知道是否有更好的方式让我忽略了?

Visual Studio 2017,.NET 4.7.1和C#7.2都可用于此。

2 个答案:

答案 0 :(得分:5)

正如评论所涵盖的那样,并非如此。但是我认为你可以建立一个完成你想要的课程(相对类型安全,使用愉快)。

public class DelegateDictionary
{
    Dictionary<Type, Delegate> Lookup;

    public DelegateDictionary()
    {
        Lookup = new Dictionary<System.Type, Delegate>();
    }

    public void Add<T>(Func<T, string, string, XmlElement> mtd)
    {
        Lookup.Add(typeof(T), mtd);
    }

    public XmlElement Invoke<T>(T value, string name, string namespaceURI)
    {
        if (!Lookup.TryGetValue(typeof(T), out var del)) throw new InvalidOperationException($"No delegate registered for {typeof(T).Name}");

        var typedDel = (Func<T, string, string, XmlElement>)del;
        return typedDel(value, name, namespaceURI);
    }
}

您必须键入Add(...)调用,无法推断,但可以推断Invoke(...)(并且可能会有更多的调用而不是注册)。

// example usage
{
    var dict = new DelegateDictionary();

    dict.Add<string>(ElementCreators.CreateElementForString);
    dict.Add<Decimal>(ElementCreators.CreateElementForDecimal);

    dict.Invoke("stringValue", "myName", "what-even-is-a-namespace");
    dict.Invoke(1.0m, "myName", "what-even-is-a-namespace");
}

我不认为你会在调用时除了演员之外支付任何费用,但还没有说明确认。

答案 1 :(得分:4)

如果您可以使用整个程序static / global,那么您可以使用非常快速的编译时字典(无查找)和泛型(无拳击):

class Program
{
    void Main()
    {
        // Set up defaults; lambda rather than Method group (allocs)
        ElementCreators<string>.CreateElement = (s, name, namespaceURI)
                => ElementCreators.CreateElement(s, name, namespaceURI);
        ElementCreators<Decimal>.CreateElement = (d, name, namespaceURI)
                => ElementCreators.CreateElement(d, name, namespaceURI);

        // Call
        XmlElement xml = ElementCreators<string>.CreateElement("hello", "name", "ns");
    }

}

public static class ElementCreators<T>
{
    // Can change property get to throw KeyNotFound if null
    public static Func<T, string, string, XmlElement> CreateElement { get; set; }
}


public static class ElementCreators
{
    public static XmlElement CreateElement(string s, string name, string namespaceURI)
    {
        return null;
    }

    public static XmlElement CreateElement(Decimal d, string name, string namespaceURI)
    {
        return null;
    }
}