C# 委托配置 Action<T> 在派生 class 的一般类型集合上

C# delegate configuration Action<T> on a generically type collection of a derived class

我正在努力向类型为抽象基础的集合提供委托配置操作 class。

派生的 classes 在集合中注册,在 运行 时,选择、初始化正确的基础 class,我希望我能传递一个操作来配置从 CreateInstance 调用传入的公共参数之外的实例。

下面的示例非常接近我想要实现的目标。在我试图将 Action<T> 分配给 FooDefinition<T> 对象的 RegisterDefinition 方法中,车轮从公共汽车上掉下来。我尝试了各种方法,包括使用接口代替和/或与抽象 class 和 Func 结合使用,但未能正确连接。

对于上下文,Bar class 代表某种处理器,它将接收进入其中的内容,并将其传递给相应的 FooBase class .派生的 classes 将知道如何处理特定的东西,但可能共享资源(如阻塞队列)。 FooOne 可能与 FooTwo 共享阻塞队列,但 FooThree 不共享 - 因此需要在注册时配置它们。

class Program
    {
        static void Main(string[] args)
        {
            var bar = new Bar();

            // Register Foos
            bar.RegisterDefinition<FooOne>("1");
            bar.RegisterDefinition<FooTwo>("2", a => a.fooTwoResource = "McFoo");
            bar.RegisterDefinition<FooThree>("3", a => {
                a.fooThreeResource_1 = "McFoo";
                a.fooThreeResource_2 = "McBar";
            });

            // Foo user selection
            Console.WriteLine("Do you want FooOne(1), or FooTwo(2), or FooTwo(3)");
            var fooSelection = Console.ReadLine();
            
            if (fooSelection == "1" || fooSelection == "2" || fooSelection == "3")
            {
                bar.RunFoo(fooSelection);
            }
            else
            {
                Console.WriteLine("Invalid foo, no bar for you!");
            }

            Console.ReadLine();
        }
    }

    public class Bar
    {
        private List<FooDefinition<FooBase>> fooDefinitions;

        public Bar()
        {
            fooDefinitions = new List<FooDefinition<FooBase>>();
        }

        public void RegisterDefinition<T>(string name) where T : FooBase
        {
            RegisterDefinition<T>(name, null);
        }

        public void RegisterDefinition<T>(string name, Action<T> configAction) where T : FooBase
        {
            if (string.IsNullOrEmpty(name)) throw new ArgumentException("You must name the foo");
            if (fooDefinitions.Any(p => p.Name == name)) throw new InvalidOperationException("Unable to duplicate foo");

            fooDefinitions.Add(
                new FooDefinition<FooBase>()
                {
                    Name = name,
                    DerivedFoo = typeof(T),
                    //ConfigAction = configAction // Cannot implicitly convert type 'System.Action<T>' to 'System.Action<InitializationAction<FooBase>>'.
                    //ConfigAction = (Action<FooBase>)configAction // Compiles, but results in an InvalidCastException when run. 'Unable to cast object of type 'System.Action`1[InitializationAction.FooTwo]' to type 'System.Action`1[InitializationAction.FooBase]'.'
                });
        }

        public void RunFoo(string fooName)
        {
            var fooDefinition = fooDefinitions.FirstOrDefault(p => p.Name == fooName);
            if (fooDefinition == null) return;

            // Create a new instance of the class, and pass in a common resource (in this example, a string).
            var fooInstance = Activator.CreateInstance(fooDefinition.DerivedFoo, "foo") as FooBase;

            // Run an action that would set any additional properties as necessary
            fooDefinition.ConfigAction?.Invoke(fooInstance);

            fooInstance.Bared();
        }
    }

    public class FooDefinition<T> where T : FooBase
    {
        public string Name { get; set; }
        public Type DerivedFoo { get; set; }
        public Action<T> ConfigAction { get; set; }
    }

    // I tried an interface, it did not work.
    //public interface IFoo
    //{
    //    string fooString { get; }
    //    void Go();
    //}

    //public abstract class FooBase : IFoo
    public abstract class FooBase
    {
        public string fooString { get; private set; }

        public FooBase(string fooString)
        {
            this.fooString = fooString;
        }

        public abstract void Bared();
    }

    public class FooOne : FooBase
    {
        public FooOne(string fooString) : base(fooString) { }

        public override void Bared()
        {
            Console.WriteLine($"DerivedFoo: {nameof(FooOne)}: {fooString}");
        }
    }

    public class FooTwo : FooBase
    {
        public string fooTwoResource { get; set; }

        public FooTwo(string fooString) : base(fooString) { }

        public override void Bared()
        {
            Console.WriteLine($"DerivedFoo: {nameof(FooTwo)}: {fooString} {fooTwoResource}");
        }
    }

    public class FooThree : FooBase
    {
        public string fooThreeResource_1 { get; set; }
        public string fooThreeResource_2 { get; set; }

        public FooThree(string fooString) : base(fooString){ }

        public override void Bared()
        {
            Console.WriteLine($"DerivedFoo: {nameof(FooThree)}: {fooString} {fooThreeResource_1} {fooThreeResource_2}");
        }
    }

虽然我意识到我可以在创建实例时打开类型,但这会将 Bar class 与每个派生的 ```FooBase`` 的实现要求相结合 class,解决方案似乎并不干净。

核心问题似乎是关于如何将 Action<T> 转换为 Action<FooBase>。您必须放弃类型安全并强制转换参数:

ConfigAction = x => configAction?.Invoke((T)x)

此外,我认为 FooDefinition 根本没有任何意义应该是通用的。这样你就不会再得到 type-safe 了,因为你似乎已经从 window 中抛出了 type-safety:没有编译时检查 DerivedFoo 会是 typeof(T),在吗?另外,您只使用 FooDefinition<FooBase> 而不是 FooDefinition<AnythingElse>,因此 FooDefinition 可能只是:

public class FooDefinition
{
    public string Name { get; set; }
    public Type DerivedFoo { get; set; }
    public Action<FooBase> ConfigAction { get; set; }
}

为了完整起见,这里 Bar

public class Bar
{
    // note the change in type
    private List<FooDefinition> fooDefinitions;

    public Bar()
    {
        fooDefinitions = new List<FooDefinition>();
    }

    public void RegisterDefinition<T>(string name) where T : FooBase
    {
        RegisterDefinition<T>(name, null);
    }

    public void RegisterDefinition<T>(string name, Action<T> configAction) where T : FooBase
    {
        if (string.IsNullOrEmpty(name)) throw new ArgumentException("You must name the foo");
        if (fooDefinitions.Any(p => p.Name == name)) throw new InvalidOperationException("Unable to duplicate foo");

        fooDefinitions.Add(
            new FooDefinition()
            {
                Name = name,
                DerivedFoo = typeof(T),
                ConfigAction = x => configAction?.Invoke((T)x)
            });
    }

    public void RunFoo(string fooName)
    {
        var fooDefinition = fooDefinitions.FirstOrDefault(p => p.Name == fooName);
        if (fooDefinition == null) return;

        // Create a new instance of the class, and pass in a common resource (in this example, a string).
        var fooInstance = Activator.CreateInstance(fooDefinition.DerivedFoo, "foo") as FooBase;

        // Run an action that would set any additional properties as necessary
        fooDefinition.ConfigAction?.Invoke(fooInstance);

        fooInstance.Bared();
    }
}

我最终将 MinIoC 用于 DI,然后在派生的 class 构造函数中解决了所需的服务。

static void Main(string[] args)
        {
            var bar = new Bar();

            // Register Services
            bar.Container.Register<FooService>();

            // Register Foos
            bar.RegisterFoo<FooOne>("1");
            bar.RegisterFoo<FooTwo>("2");

            // Foo user selection
            Console.WriteLine("Do you want FooOne(1), or FooTwo(2)");
            var fooSelection = Console.ReadLine();
            
            if (fooSelection == "1" || fooSelection == "2")
            {
                bar.RunFoo(fooSelection);
            }
            else
            {
                Console.WriteLine("Invalid foo, no bar for you!");
            }

            Console.ReadLine();
        }
    }

    public class Bar
    {
        private Dictionary<string, Type> registeredFoos;
        public Container Container { get; private set; } 

        public Bar()
        {
            Container = new Container();
            registeredFoos = new Dictionary<string, Type>();
        }

        public void RegisterFoo<T>(string name) where T : FooBase
        {
            registeredFoos.Add(name, typeof(T));
        }

        public void RunFoo(string fooName)
        {
            if (registeredFoos.TryGetValue(fooName, out var fooType))
            {
                var fooInstance = Activator.CreateInstance(fooType, new object[] { "foo", Container }) as FooBase;
                fooInstance?.Bared();
            }
        }
    }

    public abstract class FooBase
    {
        protected Container _container;

        public string fooString { get; private set; }

        public FooBase(string fooString, Container container)
        {
            this.fooString = fooString;
            _container = container;
        }

        public abstract void Bared();
    }

    public class FooOne : FooBase
    {
        public FooOne(string fooString, Container serviceContainer) : base(fooString, serviceContainer) { }

        public override void Bared()
        {
            Console.WriteLine($"DerivedFoo: {nameof(FooOne)}: {fooString}");
        }
    }

    public class FooTwo : FooBase
    {
        private readonly FooService fooService;

        public FooTwo(string fooString, Container container) : base(fooString, container) 
        {
            fooService = container.Resolve<FooService>();
        }

        public override void Bared()
        {
            var something = fooService.GetSomething();

            Console.WriteLine($"DerivedFoo: {nameof(FooTwo)}: {fooString} {something}");
        }
    }

    public class FooService
    {
        public string GetSomething()
        {
            return "something";
        }
    }