【问题标题】:List of generic delegates (e.g. System.Converter<TInput, TOutput>)通用委托列表(例如 System.Converter<TInput, TOutput>)
【发布时间】:2019-12-17 07:49:21
【问题描述】:

是否可以创建通用委托列表,例如System.Converter&lt;TInput, TOutput&gt;

在 java 中可能是 List&lt;Converter&lt;?,?&gt;&gt;,但使用 Java 的类型擦除不是很有用。似乎在 C# 中应该可以在从列表中检索后查询泛型类型并使用获得的实例来执行所需的转换。

详细说明问题:
我有用户提供的用户提供格式的数据,我在编译时对此一无所知。当然,我的程序中的算法知道如何使用它自己的特定数据类型。目标是拥有一个等效于 Java 的 List&lt;Converter&lt;?,?&gt;&gt;,其中可以安装转换器(由我或用户)并由程序自动查询以查看用户的数据是否可以转换为算法所需的格式。

【问题讨论】:

  • 您能详细说明您正在尝试做什么/您已经尝试做什么并提出一个问题吗?
  • 是的,我对这里的问题也有点模糊。
  • @MindSwipe 就像第一句话说的那样,我正在尝试创建一个List&lt;Converter&lt;?,?&gt;&gt;(或者,最好是Dictionary&lt;TIn, Converter&lt;TIn, ?&gt;&gt;),我可以查询是否存在将用户数据类型转换为其他类型的转换器数据类型。
  • 你可以,使用List&lt;Delegate&gt;,但它不是很有用,因为你“丢失”了类型信息,除非你能重新构造它。如果您将它们存储在以类型参数为键的字典中,那么您可以使用带有这些类型参数的泛型方法,查找委托,强制转换并使用它。
  • 所以,如果我理解正确的话,您需要一个转换器列表,您可以通过查询来查看实现了哪些类型的转换器?

标签: c# generics


【解决方案1】:

这是非常初级的,只是为了展示基本概念。

它有:

  • 以输入和输出类型的 2 元组为键的字典以保存转换器实例。
  • 用于注册转换器的Register 方法。注册同一对类型会覆盖以前注册的转换器。您可以轻松地将其更改为无操作或异常。
  • Convert 方法调用已注册的转换器。如果需要,创建 TryConvert 方法将非常简单。
public static class ConverterContainer
{
    private static readonly Dictionary<(Type, Type), Delegate> _converters = new Dictionary<(Type, Type), Delegate>();

    public static void Register<TInput, TOutput>(Func<TInput, TOutput> converter)
    {
        if (converter is null)
            throw new ArgumentNullException(nameof(converter));

        _converters[(typeof(TInput), typeof(TOutput))] = converter;
    }

    public static TOutput Convert<TInput, TOutput>(TInput input)
    {
        if (_converters.TryGetValue((typeof(TInput), typeof(TOutput)), out var del))
        {
            Func<TInput, TOutput> converter = (Func<TInput, TOutput>)del;

            return converter(input);
        }

        throw new InvalidOperationException("Converter not registered.");
    }
}

没有有什么:

  • 线程安全。这留给认真的实施者作为练习。
  • 可能还有其他我没有花时间考虑的事情。

使用方法:

在您的应用程序启动时,注册转换器,例如为依赖注入注册服务。

ConverterContainer.Register<long, int>(l => (int)l);
// ... etc.

无论您想在注册的输入/输出类型对之间执行转换的任何位置:

int x = ConverterContainer.Convert<long, int>(1000L)

不幸的是,您确实必须在此处指定两个类型参数。


来自 OP 的补充:

不必指定输入参数类型(这是具有可用转换的动态列表的点)在上面的示例ConverterContainer 中使用以下附加方法(正如@madreflection 在 cmets 本人中所建议的那样) :

public static TOutput Convert<TOutput>(object toConvert) {
    if (toConvert is null)
        throw new ArgumentNullException(nameof(toConvert));

    if (Converters.TryGetValue((toConvert.GetType(), typeof(TOutput)), out Delegate conv)) {
        object o = conv.DynamicInvoke(toConvert);
        return (TOutput) o;
    }

    throw new InvalidOperationException($"Converter not registered for types: {toConvert.GetType().Name} -> {typeof(TOutput).Name}");
}

您现在可以输入任何随机的object 实例,看看是否可以转换为您想要的类型。

【讨论】:

  • 这绝对是我想要的。我错过了泛型方法的使用。而作为字典键的二元组很酷!
  • 为了避免同时指定两个类型参数,您可以使用bool TryConvert&lt;TIn, TOut&gt;(TIn input, out TOut output) 方法签名。此外,您的实现是完全静态的。也许这里的单例模式更可取。
  • 请记住,这只是满足原始​​问题的技术的演示,因此有很多“也许”和“你可以”。例如,你可以实现TOutput Convert&lt;TOutput&gt;(object input),在抛出null之后,使用input.GetType()来代替typeof(TInput)查找输入类型。我几乎将其包含在我的答案中,但我认为它超出了范围,因为它与我展示的技术无关。进一步的增强功能留给读者作为练习。
  • 我完全理解给定代码的局限性,这是让我继续前进的完美示例
  • @DmitryAvtonomov:我添加了我在评论中提到的空检查。这很关键,因为 toConvert.GetType() 否则会抛出 NullReferenceException。请务必将其集成到您自己的副本中。
【解决方案2】:

不,你不能。

您必须像这样创建Generic MethodGeneric class

    void MyMethod<TInput, TOuput>()
    {
        List<Converter<TInput, TOuput>> myList = new List<Converter<TInput, TOuput>>();
    }

【讨论】:

    【解决方案3】:

    在 C# 中,列表中的所有转换器都将共享相同的类型参数(TInputTOutput)。除非您有一个非通用接口IConverter,否则您将无法将多个转换器存储在一个对象列表中。

    无论如何,您都可以尝试将元素转换为您需要的内容,就像在这里完成的那样(使用字典)。 Hirerate trough an heterogeneous and type-safe dictionary

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-12-26
      • 2021-04-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多