简体   繁体   English

C# - 在通用 Class 中,如何设置通用类型的 Function 委托?

[英]C# - In a Generic Class, How Can I Set a Generically-Typed Function Delegate?

I have a generic class of two types, "MyClass<T,U>".我有两种类型的通用 class,“MyClass<T,U>”。 Based on a parameter to the class constructor, I'd like to be able to set a "Func<T,U>" local variable in a class instance that can be called to efficiently invoke a static method with input type T and output type U. The work done on the input variable depends on the input type.基于 class 构造函数的参数,我希望能够在 class 实例中设置一个“Func<T,U>”局部变量,可以调用该实例以有效调用输入类型为 T 和 output 类型的 static 方法U. 对输入变量所做的工作取决于输入类型。 Can this be done?这可以做到吗?

Here's some code I've been playing with...这是我一直在玩的一些代码......

namespace ConsoleApp {

    public class MyClass<T, U> {
        // First constructor.  Pass in the worker function to use.
        public MyClass(Func<T, U> doWork) {
            _doWork = doWork;
        }
        // Second constructor.  Pass in a variable indicating the worker function to use.
        public MyClass(int workType) {
            if (workType == 1) _doWork = Workers.Method1;
            else if (workType == 2) _doWork = Workers.Method2;
            else throw new Exception();
        }
        // User-callable method to do the work.
        public U DoWork(T value) => _doWork(value);
        // Private instance variable with the worker delegate.
        private Func<T, U> _doWork;
    }

    public static class Workers {
        public static ushort Method1(uint value) => (ushort)(value >> 2);
        public static uint Method1(ulong value) => (uint)(value >> 1);
        public static ushort Method2(uint value) => (ushort)(value >> 3);
        public static uint Method2(ulong value) => (uint)(value >> 4);
    }

    public class Program {
        public static void Main(string[] args) {
            var mc1 = new MyClass<uint, ushort>(Workers.Method1);
            var mc2 = new MyClass<ulong, uint>(Workers.Method1);
            var mc3 = new MyClass<uint, ushort>(Workers.Method2);
            var mc4 = new MyClass<ulong, uint>(Workers.Method2);
            var mc5 = new MyClass<uint, ushort>(1);
            var mc6 = new MyClass<ulong, uint>(1);
            var mc7 = new MyClass<uint, ushort>(2);
            var mc8 = new MyClass<ulong, uint>(2);
        }
    }

}

The first constructor works just fine: the compiler is able to infer the correct overload of the static worker method to pass as a parameter, which gets stored in the instance variable _doWork, and can be (reasonably) efficiently called.第一个构造函数工作得很好:编译器能够推断 static worker 方法的正确重载作为参数传递,它存储在实例变量 _doWork 中,并且可以(合理地)有效地调用。

The second constructor won't compile, however, The problem is the assignments to _doWork which fail because "No overload for 'Method_' matches delegate 'Func<T,U>'".然而,第二个构造函数不会编译,问题是对 _doWork 的赋值失败,因为“'Method_' 的重载不匹配委托 'Func<T,U>'”。 I sort of get it but sort of don't.我有点明白,但有点不明白。 It seems the compiler knows what T and U are at compile time, is "substituting" them into the class definition when compiling, and, so, ought to be able to infer which worker method to use.似乎编译器在编译时知道 T 和 U 是什么,在编译时将它们“替换”到 class 定义中,因此,应该能够推断出要使用哪个辅助方法。 Anyone know why not?有谁知道为什么不呢?

Anyway, for reasons not worth going into, I'd really like to make the second constructor work.无论如何,出于不值得深入探讨的原因,我真的很想让第二个构造函数工作。 The obvious thing to try is to "cast" Method1 or Method2 to Func<T,U>, but delegates aren't objects and can't be cast.显而易见的尝试是将 Method1 或 Method2“强制转换”为 Func<T,U>,但委托不是对象,不能强制转换。 I've found a couple of pretty ugly ways to do it (that are also horribly inefficient), but I can't help but feeling there is something easier I'm missing.我发现了一些非常丑陋的方法来做到这一点(它们的效率也非常低),但我情不自禁地觉得我缺少了一些更容易的东西。 Any other ideas?还有其他想法吗?

EDIT: It sounds like I'm abusing generics. What I have are about 100 different combinations of possible T, U, Worker values (there's actually a fourth dimension, but ignore that), each that behave somewhat differently.编辑:听起来我在滥用 generics。我有大约 100 种可能的 T、U、Worker 值的不同组合(实际上有第四个维度,但忽略它),每个的行为都有些不同。 I'm trying to avoid having to create a separate class for each combination.我试图避免为每个组合创建一个单独的 class。 So this isn't "generics" in the sense of being able to plug in any types T and U. What, if any, alternatives are there?因此,就能够插入任何类型 T 和 U 而言,这不是“泛型”。如果有的话,有什么替代方案?

Have you considered using something like a factory pattern and resolving the service in a manner similar to this example您是否考虑过使用工厂模式之类的东西并以类似于此示例的方式解析服务

void Main()
{
    var serviceCollection = new Microsoft.Extensions.DependencyInjection.ServiceCollection();
    serviceCollection.AddSingleton<IMessageDeliveryProcessor, InAppNotificationMessageProcessor>();
    serviceCollection.AddSingleton<IMessageDeliveryProcessor, MessageProcessor>();
    serviceCollection.AddSingleton<IMessageProcessorFactory, MessageProcessorFactory>();
    
    var serviceProvider = serviceCollection.BuildServiceProvider();
    
    var factoryItem = serviceProvider.GetService<IMessageProcessorFactory>();
    var service = factoryItem.Resolve(DeliveryType.Email);
    
    service.ProcessAsync("", "", "");
}

public enum DeliveryType
{
    Email,
    InApp,
}


public class MessageProcessorFactory : IMessageProcessorFactory
{
    private readonly IServiceProvider _serviceProvider;

    public MessageProcessorFactory(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider;

    public IMessageDeliveryProcessor? Resolve(DeliveryType deliveryType)
        => _serviceProvider
            .GetServices<IMessageDeliveryProcessor>()
            .SingleOrDefault(processor => processor.DeliveryType.Equals(deliveryType));
}

public interface IMessageProcessorFactory
{
    IMessageDeliveryProcessor? Resolve(DeliveryType deliveryType);
}


public interface IMessageDeliveryProcessor
{
    
    DeliveryType DeliveryType { get; }
    Task ProcessAsync(string applicationId, string eventType, string messageBody);
}

public class InAppNotificationMessageProcessor : IMessageDeliveryProcessor
{
    public DeliveryType DeliveryType => DeliveryType.InApp;

    public Task ProcessAsync(string applicationId, string eventType, string messageBody)
    {
        Console.Write("InAppNotificationMessageProcessor");
        return Task.CompletedTask;
    }
}

public class EmailNotificationMessageProcessor : IMessageDeliveryProcessor
{
    public DeliveryType DeliveryType => DeliveryType.Email;

    public Task ProcessAsync(string applicationId, string eventType, string messageBody)
    {
        Console.Write("MessageProcessor");
        return Task.CompletedTask;
    }
}

This doesnt address your code and your issue exactly, but based on what I see of your issue, this could help you in the direction of travel.这并不能完全解决您的代码和您的问题,但根据我对您的问题的了解,这可以帮助您指明前进的方向。

In your second constructor, you are attempting to assign something not directly compatible.在你的第二个构造函数中,你试图分配一些不直接兼容的东西。 What you're assigning is a method group, of which nothing in the method group can match a T or a U using the compiler's type inference rules.您分配的是一个方法组,使用编译器的类型推断规则,方法组中的任何内容都不能匹配TU

One thing you can do is instead of trying to assign the delegates directly in your second destructor, you can instead assign a dispatcher method that will resolve this at runtime.您可以做的一件事是不要尝试直接在第二个析构函数中分配委托,而是可以分配一个将在运行时解决此问题的调度程序方法。

Your constructor could be changed to您的构造函数可以更改为

public MyClass(int workType)
{
    if (workType == 1) _doWork = Method1Dispatcher;
    else if (workType == 2) _doWork = Method2Dispatcher;
    else throw new Exception();
}

where you have dispatcher methods such as你有调度程序方法的地方,例如

public U Method1Dispatcher(T value)
{
    return value switch
    {
        uint x => (U)(object)Workers.Method1(x),
        ulong x => (U)(object)Workers.Method1(x),
        _ => throw new NotSupportedException()
    };
}

public U Method2Dispatcher(T value)
{
    return value switch
    {
        uint x => (U)(object)Workers.Method2(x),
        ulong x => (U)(object)Workers.Method2(x),
        _ => throw new NotSupportedException()
    };
}

These methods use a double cast to get around the compile-time checks that prevent you from "equating", for instance, a uint and a T .这些方法使用双重转换来绕过编译时检查,这些检查会阻止您“等同”,例如uintT Casting to object removes that constraint, and casts to another type, at runtime, could either succeed or fail.转换为 object 会移除该约束,并且在运行时转换为另一种类型可能会成功或失败。 That's not typesafe, but if implemented carefully like the above, you at least encapsulate known (to us not the compiler) safe casts.这不是类型安全的,但如果像上面那样小心地实现,你至少封装了已知的(对我们来说不是编译器)安全的转换。

To test that this works, you can modify your Main method to prove it要测试这是否有效,您可以修改Main方法来证明它

var mc5 = new MyClass<uint, ushort>(1);
var mc5Result = mc5.DoWork(5);
Console.WriteLine($"Got result {mc5Result} of type {mc5Result.GetType().Name}");
var mc6 = new MyClass<ulong, uint>(1);
var mc6Result = mc6.DoWork(6);
Console.WriteLine($"Got result {mc6Result} of type {mc6Result.GetType().Name}");
var mc7 = new MyClass<uint, ushort>(2);
var mc7Result = mc7.DoWork(7);
Console.WriteLine($"Got result {mc7Result} of type {mc7Result.GetType().Name}");
var mc8 = new MyClass<ulong, uint>(2);
var mc8Result = mc8.DoWork(8);
Console.WriteLine($"Got result {mc6Result} of type {mc8Result.GetType().Name}");

Now, while this works, it's probably not the best solution because you say there are hundreds of combinations.现在,虽然这可行,但它可能不是最佳解决方案,因为您说有数百种组合。 Perhaps you can replace the switch with a reflection based way of obtaining the correct method, and then invoking it.也许您可以用基于反射的方式替换switch ,以获取正确的方法,然后调用它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM