Dictionary<T, Func>: 如何使用 T 作为 Func 的泛型?

Dictionary<T, Func>: how to use T as Func's generic type?

不知道怎么表达清楚

我有这个界面:

interface IConverter
{
    Dictionary<Type, Func<string, object>> ConversionMethods { get; }
}

基本上,它定义了一个契约,说 class 实现它应该为它使用的所有自定义类型(无论是枚举还是任何东西)提供转换方法。

是否可以将 Func 的泛型类型中的 object 替换为其对应的字典键类型(因此不可能有两个不匹配的类型)?

我认为这是不可能的,但替代方案有点烦人(使用dynamicobject,创建一个专门的字典...)。


编辑 1:虚构的使用示例

interface IConverter
{
    Dictionary<Type, Func<string, object>> GetConversionMethods();
}

enum A
{
    AA,AB,AC
}

enum B
{
    BA, BB, BC
}

class blah : IConverter
{
    public Dictionary<Type, Func<string, object>> GetConversionMethods()
    {
        var d = new Dictionary<Type, Func<string, object>>
        {
            {
                typeof(A),
                (s) =>
                    {
                        // here, I could return whatever I want because the 'Func' returns 'object'
                        return s == "AA" ? A.AA : s == "AB" ? A.AB : A.AC;
                    }
            },
            {
                typeof(B),
                (s) =>
                    {
                        // same
                        return s == "BA" ? B.BA : s == "BB" ? B.BB : B.BC;
                    }
            }
        };
        return d;
    }

    void blahah()
    {
        // and here, I also get an `object`, where I would like to have a A
        GetConversionMethods()[typeof(A)]("123");
    }
}

这取决于您可以在多大程度上更改签名,但至少接口可以强制使用通用类型。如何添加强类型转换器是实现者的责任,而不是调用者的责任。

实现本身可以使用任何结构来提供转换器。事件 if...else,但要在幕后使用字典,可以使用 Dictionary<Type, Delegate> 类型的字典,其中可以添加强类型转换器。下面的示例使用辅助 set<T> 函数来确保字典以预期的方式设置。

interface IConverter
{
    Func<string,T> GetConverter<T>(); //the method returned is always strongly typed, so the caller is never responsible for type checking
}

enum A{AA,AB,AC}    
enum B{BA, BB, BC}

class blah : IConverter
{
    public Func<string,T> GetConverter<T>()
    {
        if(methods.TryGetValue(typeof(T), out var fn)) //side note, out var fn will not work in older visual studio versions. In that case declare fn before this line
            return (Func<string,T>)fn; //the set<T> method ensures that this conversion is safe
        throw new NotImplementedException(); 
    }

    public blah()
    {
        set<A>(s => s == "AA" ? A.AA : s == "AB" ? A.AB : A.AC); //copied from the example. Enum.Parse could perhaps be used instead
        set<B>(s => s == "BA" ? B.BA : s == "BB" ? B.BB : B.BC);
    }

    Dictionary<Type, Delegate> methods= new Dictionary<Type, Delegate>(); // Delegate can be used as a type to handle all lambda's. It's the implementers responsibility to handle with care. Something like the set<T> helper method is recommended

    void set<T>(Func<string,T> fn) //helper method to assign the strongly typed methods to the specific type
    {
        methods[typeof(T)] = fn;
    }
}

static void blahah()
{
    new blah().GetConverter<A>()("123");
}

这有点复杂,但它确实有效。

首先,您需要将转换 Func 封装在 class 中,这样您就可以更轻松地处理它们,而不会暴露它们所有不同类型的参数。然后,您需要定义接口或基础 classes 以隐藏各种通用参数,避免它们引起问题,并允许您将不同的转换器放在同一个集合中。然后,您将需要各种方法让各种转换器在不直接使用这些类型参数的情况下指示它们使用的类型。然后,您只需要将其全部包装在 class 中,并使用一种可以根据需要找到合适的转换器的方法。

我会引导你完成它。

首先,这个基础 class 将是我们处理转换器的方式,而不用担心它的通用类型参数,但仍然知道它使用什么类型。

public abstract class OneWayTypeConverterBase : IConvertFromType, IConvertToType
{
    public abstract Type AcceptsType { get; }
    public abstract Type ReturnsType { get; }
}

现在我们继承自该基础 class。这是执行实际转换工作的 class;您可以使用执行您需要的任何转换操作的 lambda 实例化它。请注意,它实现了我们上面定义的属性。

public class OneWayTypeConverter<TSource, TTarget> : OneWayTypeConverterBase
{
    public OneWayTypeConverter(Func<TSource, TTarget> conversionMethod)
    {
        _conversionMethod = conversionMethod;
    }

    public override Type AcceptsType => typeof(TSource);
    public override Type ReturnsType => typeof(TTarget);

    private readonly Func<TSource, TTarget> _conversionMethod;

    public TTarget Convert(TSource sourceObject)
    {
        return _conversionMethod(sourceObject);
    }
}

现在我们需要一个地方来保存所有这些,以便使用代码有一个入口点。为简单起见,我让它接受了一个转换器的平面集合,然后将它们全部归档到嵌套字典中,这样它就可以稍后进行查找,而不必一直调用 typeof

public class TypeConverter
{
    public TypeConverter(IEnumerable<OneWayTypeConverterBase> converters)
    {
        _converters = converters
            .GroupBy(x => x.AcceptsType)
            .ToDictionary(
                kSource => kSource.Key,
                vSource => vSource
                    .ToDictionary(kTarget => kTarget.ReturnsType, vTarget => vTarget));
    }

    private Dictionary<Type, Dictionary<Type, OneWayTypeConverterBase>> _converters;

    public TTarget ConvertType<TSource, TTarget>(TSource sourceObject)
    {
        Dictionary<Type, OneWayTypeConverterBase> baseConverters;

        if (_converters.TryGetValue(sourceObject.GetType(), out baseConverters))
        {
            OneWayTypeConverterBase baseConverter;

            if (baseConverters.TryGetValue(typeof(TTarget), out baseConverter))
            {
                OneWayTypeConverter<TSource, TTarget> converter = baseConverter as OneWayTypeConverter<TSource, TTarget>;

                if (converter != null)
                {
                    return converter.Convert(sourceObject);
                }
            }

            throw new InvalidOperationException("No converter found for that target type");
        }
        else
        {
            throw new InvalidOperationException("No converters found for that source type");
        }
    }
}

所以现在,您可以这样设置:

var converter = new TypeConverter(new List<OneWayTypeConverterBase>
{
    new OneWayTypeConverter<int, string>(x => $"The number was {x}"),
    new OneWayTypeConverter<int, bool>(x => x != 0),
    new OneWayTypeConverter<bool, string>(x => $"The bool was {x}")
});

然后当你需要它的时候,你可以像这样使用它:

var result = converter.ConvertType<int, string>(4);