【问题标题】:Dictionary<T, Func>: how to use T as Func's generic type?Dictionary<T, Func>:如何使用 T 作为 Func 的泛型类型?
【发布时间】:2017-05-19 14:44:40
【问题描述】:

不知道怎么表达清楚。

我有这个界面:

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

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

是否可以将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");
    }
}

【问题讨论】:

  • 如果可能的话,您能否发布一个示例代码 sn-p,说明您打算如何访问这样一个虚构的字典?
  • @Kilazur,“是否可以在 Func 的泛型类型中替换 Type”...你的意思是替换 object
  • 编译时不可能。
  • 到目前为止发布的示例显示了您将如何填写字典。但是在该代码中,使用objectdynamic 键入函数还不会像您说的那样“烦人”,对吗?我想一旦你尝试从字典中阅读,当你必须投射时,烦人的部分就会随之而来——你能举一个这种用法的例子吗?具体来说,我想知道您是否会知道在编译时检索的类型(在这种情况下,您可能不需要字典),或者是否只在运行时知道(在这种情况下,我无法想象您如何安全地使用结果类型) .
  • 这与我之前尝试过的东西有关。你可以靠近,但不能通过这条路线。基本上,可以使用泛型来强制执行您所追求的要求,但不能仅使用现有类型;您将需要一些自定义类型。我会看看我是否可以提炼出一个可以作为答案的例子。

标签: c# dictionary generics func


【解决方案1】:

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

首先,您需要将转换Funcs 封装在类中,这样您就可以更轻松地处理它们,并且不会暴露它们所有不同的类型参数。然后你需要定义接口或基类来隐藏各种泛型参数,它们会导致问题,并允许你将不同的转换器放在同一个集合中。然后,您将需要各种方法来指示它们使用的类型,而无需直接使用这些类型参数。然后,您只需将其全部包装在一个类中,并使用一种方法,该方法可以根据需要为您找到正确的转换器。

我会引导你完成它。

首先,这个基类将成为我们处理转换器的方式,无需担心它的泛型类型参数,但仍然知道它使用什么类型。

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

现在我们从那个基类继承。这是进行实际转换工作的类;您可以使用 lambda 实例化它,该 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);

【讨论】:

  • 实际上看起来几乎完美。唯一的缺点是 IEnumerable 允许单一类型使用多种方法,但这是可管理的。我稍后会测试它,但它看起来非常好。
  • 在转换为字典时,您可以轻松检查相同类型的多个转换器并抛出异常,或者您可以提供一种方法来一次添加一个转换器并检查现有转换器在添加新的类型之前。后者将是我的首选方法,但构造方法仍然很好,因此您可以快速设置TypeConverter,所以我可能会同时实现这两种方法。如果您愿意,我可以将其添加到我的代码示例中?
  • 啊,它实际上对我不起作用;我想要的似乎是不可能的,因为在某些时候我需要将变量用作泛型类型,而语言不支持这种类型。如果我可以从 ConvertType 中删除泛型类型,您的示例将起作用
  • 如果你在 ConvertType 上没有类型参数,编译器无法判断它会返回什么类型 - 你会被困在返回 objectdynamic,或者可能是一些IConvertible 基本接口,这同样含糊不清。您可以更改该方法,以便它采用 Type 的实例而不是那些类型参数,并受到它们的适当约束,但是如果没有类型参数,总会有一个点,您必须退回到 object 或其他东西。
  • 如果您无法获取类型参数,您的其他选择是使用反射来使用您必须构造泛型类型参数的 Type 实例,或者使用每种类型的实例(或使用Activator.CreateInstance() 来获取实例)然后让类型推断将您带到那里。
【解决方案2】:

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

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

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

【讨论】:

  • 有趣,但是接口必须有办法返回所有的转换方法,所以我可以在其他地方使用它们,而不必事先知道类型。
  • @Kilazur 除非您使用动态或对象或所有类型的通用接口,否则这是不可能的
  • @Zbigniew 这也是我的猜测
  • 您可以让接口/类也以单独的方法返回所有委托(字典值)。就我个人而言,我会将对转换方法的直接调用保持为强类型。但如果目标是更多扩展,围绕转换器本身的额外包装器可能会更可取。
猜你喜欢
  • 2014-11-29
  • 2013-04-19
  • 1970-01-01
  • 1970-01-01
  • 2011-08-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多