在运行时为接口创建类(C#)

发布于 2024-07-30 06:01:35 字数 595 浏览 3 评论 0原文

我正在考虑获取一组对象,假设目前有 3 个对象处于活动状态,它们都实现了一个公共接口,然后将这些对象包装在第四个对象中,也实现了相同的接口。

第四个对象的方法和属性的实现将简单地调用这 3 个底层对象上的相关位。 我知道在某些情况下这样做是没有意义的,但这是针对服务多播架构的,因此已经存在一组很好的限制。

我的问题是从哪里开始。 第四个对象的生成应该在运行时在内存中完成,所以我在考虑Reflection.Emit,不幸的是我没有足够的经验,甚至不知道从哪里开始。

我必须构建内存中程序集吗? 看起来确实是这样,但我只是想要一个快速指示我应该从哪里开始。

基本上,我正在考虑采用一个接口,以及全部实现该接口的对象实例列表,并构造一个新对象,还实现该接口,这应该“多播”所有方法调用和对所有底层对象的属性访问,位于尽可能少。 会有很多异常等问题,但当我遇到这些问题时我会解决这些问题。

这是面向服务的体系结构,我希望现有的代码以记录器服务为例,现在可以访问多个记录器服务,而不必更改使用这些服务的代码。 相反,我想在运行时生成一个记录器服务包装器,它在内部简单地调用多个底层对象上的相关方法。

这是针对 .NET 3.5 和 C# 的。

I'm looking at taking a set of objects, let's say there's 3 objects alive at the moment, which all implement a common interface, and then wrap those objects inside a fourth object, also implementing the same interface.

The fourth object's implementations of methods and properties would simply call the relevant bits on those 3 underlying objects. I know that there will be cases here where it won't make sense to do that, but this is for a service multicast architecture so there's already a good set of limitations in place.

My question is where to start. The generation of that fourth object should be done in memory, at runtime, so I'm thinking Reflection.Emit, unfortunately I don't have enough experience with that to even know where to begin.

Do I have to construct an in-memory assembly? It sure looks that way, but I'd just like a quick pointer to where I should start.

Basically I'm looking at taking an interface, and a list of object instances all implementing that interface, and constructing a new object, also implementing that interface, which should "multicast" all method calls and property access to all the underlying objects, at least as much as possible. There will be heaps of problems with exceptions and such but I'll tackle those bits when I get to them.

This is for a service-oriented architecture, where I would like to have existing code that takes, as an example, a logger-service, to now access multiple logger services, without having to change the code that uses the services. Instead, I'd like to runtime-generate a logger-service-wrapper that internally simply calls the relevant methods on multiple underlying objects.

This is for .NET 3.5 and C#.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

彼岸花ソ最美的依靠 2024-08-06 06:01:36

您真的需要在运行时创建程序集吗?

也许你不需要它。

c# 为您提供 Action、is 运算符和 lambda/delegates...

Did you really need to create the assembly at runtime?

Probably you don't need it.

c# give you Action<T>, the is operator and lambda/delegates...

我的奇迹 2024-08-06 06:01:35

如果有人感兴趣,我将在这里发布我自己的实现。

这深受我接受的马克答案的影响和复制。

该代码可用于包装一组对象,所有对象都实现一个公共接口,在一个新对象内,也实现所述接口。 当访问返回对象上的方法和属性时,将以相同的方式访问底层对象上相应的方法和属性。

这里有龙:这是针对特定用途的。 这可能会出现奇怪的问题,特别是因为代码不能确保所有底层对象都被赋予与被调用者传递的完全相同的对象(或者更确切地说,它不会禁止底层对象之一弄乱参数) ,对于有返回值的方法,只会返回最后一个返回值。 至于 out/ref 参数,我什至没有测试过它是如何工作的,但它可能不会。 您已收到警告。

#region Using

using System;
using System.Linq;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using LVK.Collections;

#endregion

namespace LVK.IoC
{
    /// <summary>
    /// This class implements a service wrapper that can wrap multiple services into a single multicast
    /// service, that will in turn dispatch all method calls down into all the underlying services.
    /// </summary>
    /// <remarks>
    /// This code is heavily influenced and copied from Marc Gravell's implementation which he
    /// posted on Stack Overflow here: http://stackoverflow.com/questions/847809
    /// </remarks>
    public static class MulticastService
    {
        /// <summary>
        /// Wrap the specified services in a single multicast service object.
        /// </summary>
        /// <typeparam name="TService">
        /// The type of service to implement a multicast service for.
        /// </typeparam>
        /// <param name="services">
        /// The underlying service objects to multicast all method calls to.
        /// </param>
        /// <returns>
        /// The multicast service instance.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="services"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="services"/> contains a <c>null</c> reference.</para>
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <para><typeparamref name="TService"/> is not an interface type.</para>
        /// </exception>
        public static TService Wrap<TService>(params TService[] services)
            where TService: class
        {
            return (TService)Wrap(typeof(TService), (Object[])services);
        }

        /// <summary>
        /// Wrap the specified services in a single multicast service object.
        /// </summary>
        /// <param name="serviceInterfaceType">
        /// The <see cref="Type"/> object for the service interface to implement a multicast service for.
        /// </param>
        /// <param name="services">
        /// The underlying service objects to multicast all method calls to.
        /// </param>
        /// <returns>
        /// The multicast service instance.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="serviceInterfaceType"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="services"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="services"/> contains a <c>null</c> reference.</para>
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <para><typeparamref name="TService"/> is not an interface type.</para>
        /// </exception>
        /// <exception cref="InvalidOperationException">
        /// <para>One or more of the service objects in <paramref name="services"/> does not implement
        /// the <paramref name="serviceInterfaceType"/> interface.</para>
        /// </exception>
        public static Object Wrap(Type serviceInterfaceType, params Object[] services)
        {
            #region Parameter Validation

            if (Object.ReferenceEquals(null, serviceInterfaceType))
                throw new ArgumentNullException("serviceInterfaceType");
            if (!serviceInterfaceType.IsInterface)
                throw new ArgumentException("serviceInterfaceType");
            if (Object.ReferenceEquals(null, services) || services.Length == 0)
                throw new ArgumentNullException("services");
            foreach (var service in services)
            {
                if (Object.ReferenceEquals(null, service))
                    throw new ArgumentNullException("services");
                if (!serviceInterfaceType.IsAssignableFrom(service.GetType()))
                    throw new InvalidOperationException("One of the specified services does not implement the specified service interface");
            }

            #endregion

            if (services.Length == 1)
                return services[0];

            AssemblyName assemblyName = new AssemblyName(String.Format("tmp_{0}", serviceInterfaceType.FullName));
            String moduleName = String.Format("{0}.dll", assemblyName.Name);
            String ns = serviceInterfaceType.Namespace;
            if (!String.IsNullOrEmpty(ns))
                ns += ".";

            var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName,
                AssemblyBuilderAccess.RunAndSave);
            var module = assembly.DefineDynamicModule(moduleName, false);
            var type = module.DefineType(String.Format("{0}Multicast_{1}", ns, serviceInterfaceType.Name),
                TypeAttributes.Class |
                TypeAttributes.AnsiClass |
                TypeAttributes.Sealed |
                TypeAttributes.NotPublic);
            type.AddInterfaceImplementation(serviceInterfaceType);

            var ar = Array.CreateInstance(serviceInterfaceType, services.Length);
            for (Int32 index = 0; index < services.Length; index++)
                ar.SetValue(services[index], index);

            // Define _Service0..N-1 private service fields
            FieldBuilder[] fields = new FieldBuilder[services.Length];
            var cab = new CustomAttributeBuilder(
                typeof(DebuggerBrowsableAttribute).GetConstructor(new Type[] { typeof(DebuggerBrowsableState) }),
                new Object[] { DebuggerBrowsableState.Never });
            for (Int32 index = 0; index < services.Length; index++)
            {
                fields[index] = type.DefineField(String.Format("_Service{0}", index),
                    serviceInterfaceType, FieldAttributes.Private);

                // Ensure the field don't show up in the debugger tooltips
                fields[index].SetCustomAttribute(cab);
            }

            // Define a simple constructor that takes all our services as arguments
            var ctor = type.DefineConstructor(MethodAttributes.Public,
                CallingConventions.HasThis,
                Sequences.Repeat(serviceInterfaceType, services.Length).ToArray());
            var generator = ctor.GetILGenerator();

            // Store each service into its own fields
            for (Int32 index = 0; index < services.Length; index++)
            {
                generator.Emit(OpCodes.Ldarg_0);
                switch (index)
                {
                    case 0:
                        generator.Emit(OpCodes.Ldarg_1);
                        break;

                    case 1:
                        generator.Emit(OpCodes.Ldarg_2);
                        break;

                    case 2:
                        generator.Emit(OpCodes.Ldarg_3);
                        break;

                    default:
                        generator.Emit(OpCodes.Ldarg, index + 1);
                        break;
                }
                generator.Emit(OpCodes.Stfld, fields[index]);
            }
            generator.Emit(OpCodes.Ret);

            // Implement all the methods of the interface
            foreach (var method in serviceInterfaceType.GetMethods())
            {
                var args = method.GetParameters();
                var methodImpl = type.DefineMethod(method.Name,
                    MethodAttributes.Private | MethodAttributes.Virtual,
                    method.ReturnType, (from arg in args select arg.ParameterType).ToArray());
                type.DefineMethodOverride(methodImpl, method);

                // Generate code to simply call down into each service object
                // Any return values are discarded, except the last one, which is returned
                generator = methodImpl.GetILGenerator();
                for (Int32 index = 0; index < services.Length; index++)
                {
                    generator.Emit(OpCodes.Ldarg_0);
                    generator.Emit(OpCodes.Ldfld, fields[index]);
                    for (Int32 paramIndex = 0; paramIndex < args.Length; paramIndex++)
                    {
                        switch (paramIndex)
                        {
                            case 0:
                                generator.Emit(OpCodes.Ldarg_1);
                                break;

                            case 1:
                                generator.Emit(OpCodes.Ldarg_2);
                                break;

                            case 2:
                                generator.Emit(OpCodes.Ldarg_3);
                                break;

                            default:
                                generator.Emit((paramIndex < 255)
                                    ? OpCodes.Ldarg_S
                                    : OpCodes.Ldarg,
                                    paramIndex + 1);
                                break;
                        }

                    }
                    generator.Emit(OpCodes.Callvirt, method);
                    if (method.ReturnType != typeof(void) && index < services.Length - 1)
                        generator.Emit(OpCodes.Pop); // discard N-1 return values
                }
                generator.Emit(OpCodes.Ret);
            }

            return Activator.CreateInstance(type.CreateType(), services);
        }
    }
}

I'll post my own implementation here, if anyone's interested.

This is heavily influenced and copied from Marc's answer, which I accepted.

The code can be used to wrap a set of objects, all implementing a common interface, inside a new object, also implementing said interface. When methods and properties on the returned object is accessed, the corresponding methods and properties on the underlying objects are accessed in the same way.

Here there be dragons: This is for a specific usage. There's potential for odd problems with this, in particular since the code does not ensure that all underlying objects are given the exact same objects as the callee is passing (or rather, it does not prohibit one of the underlying objects from messing with the arguments), and for methods that returns a value, only the last return value will be returned. As for out/ref arguments, I haven't even tested how that works, but it probably doesn't. You have been warned.

#region Using

using System;
using System.Linq;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using LVK.Collections;

#endregion

namespace LVK.IoC
{
    /// <summary>
    /// This class implements a service wrapper that can wrap multiple services into a single multicast
    /// service, that will in turn dispatch all method calls down into all the underlying services.
    /// </summary>
    /// <remarks>
    /// This code is heavily influenced and copied from Marc Gravell's implementation which he
    /// posted on Stack Overflow here: http://stackoverflow.com/questions/847809
    /// </remarks>
    public static class MulticastService
    {
        /// <summary>
        /// Wrap the specified services in a single multicast service object.
        /// </summary>
        /// <typeparam name="TService">
        /// The type of service to implement a multicast service for.
        /// </typeparam>
        /// <param name="services">
        /// The underlying service objects to multicast all method calls to.
        /// </param>
        /// <returns>
        /// The multicast service instance.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="services"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="services"/> contains a <c>null</c> reference.</para>
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <para><typeparamref name="TService"/> is not an interface type.</para>
        /// </exception>
        public static TService Wrap<TService>(params TService[] services)
            where TService: class
        {
            return (TService)Wrap(typeof(TService), (Object[])services);
        }

        /// <summary>
        /// Wrap the specified services in a single multicast service object.
        /// </summary>
        /// <param name="serviceInterfaceType">
        /// The <see cref="Type"/> object for the service interface to implement a multicast service for.
        /// </param>
        /// <param name="services">
        /// The underlying service objects to multicast all method calls to.
        /// </param>
        /// <returns>
        /// The multicast service instance.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="serviceInterfaceType"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="services"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="services"/> contains a <c>null</c> reference.</para>
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <para><typeparamref name="TService"/> is not an interface type.</para>
        /// </exception>
        /// <exception cref="InvalidOperationException">
        /// <para>One or more of the service objects in <paramref name="services"/> does not implement
        /// the <paramref name="serviceInterfaceType"/> interface.</para>
        /// </exception>
        public static Object Wrap(Type serviceInterfaceType, params Object[] services)
        {
            #region Parameter Validation

            if (Object.ReferenceEquals(null, serviceInterfaceType))
                throw new ArgumentNullException("serviceInterfaceType");
            if (!serviceInterfaceType.IsInterface)
                throw new ArgumentException("serviceInterfaceType");
            if (Object.ReferenceEquals(null, services) || services.Length == 0)
                throw new ArgumentNullException("services");
            foreach (var service in services)
            {
                if (Object.ReferenceEquals(null, service))
                    throw new ArgumentNullException("services");
                if (!serviceInterfaceType.IsAssignableFrom(service.GetType()))
                    throw new InvalidOperationException("One of the specified services does not implement the specified service interface");
            }

            #endregion

            if (services.Length == 1)
                return services[0];

            AssemblyName assemblyName = new AssemblyName(String.Format("tmp_{0}", serviceInterfaceType.FullName));
            String moduleName = String.Format("{0}.dll", assemblyName.Name);
            String ns = serviceInterfaceType.Namespace;
            if (!String.IsNullOrEmpty(ns))
                ns += ".";

            var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName,
                AssemblyBuilderAccess.RunAndSave);
            var module = assembly.DefineDynamicModule(moduleName, false);
            var type = module.DefineType(String.Format("{0}Multicast_{1}", ns, serviceInterfaceType.Name),
                TypeAttributes.Class |
                TypeAttributes.AnsiClass |
                TypeAttributes.Sealed |
                TypeAttributes.NotPublic);
            type.AddInterfaceImplementation(serviceInterfaceType);

            var ar = Array.CreateInstance(serviceInterfaceType, services.Length);
            for (Int32 index = 0; index < services.Length; index++)
                ar.SetValue(services[index], index);

            // Define _Service0..N-1 private service fields
            FieldBuilder[] fields = new FieldBuilder[services.Length];
            var cab = new CustomAttributeBuilder(
                typeof(DebuggerBrowsableAttribute).GetConstructor(new Type[] { typeof(DebuggerBrowsableState) }),
                new Object[] { DebuggerBrowsableState.Never });
            for (Int32 index = 0; index < services.Length; index++)
            {
                fields[index] = type.DefineField(String.Format("_Service{0}", index),
                    serviceInterfaceType, FieldAttributes.Private);

                // Ensure the field don't show up in the debugger tooltips
                fields[index].SetCustomAttribute(cab);
            }

            // Define a simple constructor that takes all our services as arguments
            var ctor = type.DefineConstructor(MethodAttributes.Public,
                CallingConventions.HasThis,
                Sequences.Repeat(serviceInterfaceType, services.Length).ToArray());
            var generator = ctor.GetILGenerator();

            // Store each service into its own fields
            for (Int32 index = 0; index < services.Length; index++)
            {
                generator.Emit(OpCodes.Ldarg_0);
                switch (index)
                {
                    case 0:
                        generator.Emit(OpCodes.Ldarg_1);
                        break;

                    case 1:
                        generator.Emit(OpCodes.Ldarg_2);
                        break;

                    case 2:
                        generator.Emit(OpCodes.Ldarg_3);
                        break;

                    default:
                        generator.Emit(OpCodes.Ldarg, index + 1);
                        break;
                }
                generator.Emit(OpCodes.Stfld, fields[index]);
            }
            generator.Emit(OpCodes.Ret);

            // Implement all the methods of the interface
            foreach (var method in serviceInterfaceType.GetMethods())
            {
                var args = method.GetParameters();
                var methodImpl = type.DefineMethod(method.Name,
                    MethodAttributes.Private | MethodAttributes.Virtual,
                    method.ReturnType, (from arg in args select arg.ParameterType).ToArray());
                type.DefineMethodOverride(methodImpl, method);

                // Generate code to simply call down into each service object
                // Any return values are discarded, except the last one, which is returned
                generator = methodImpl.GetILGenerator();
                for (Int32 index = 0; index < services.Length; index++)
                {
                    generator.Emit(OpCodes.Ldarg_0);
                    generator.Emit(OpCodes.Ldfld, fields[index]);
                    for (Int32 paramIndex = 0; paramIndex < args.Length; paramIndex++)
                    {
                        switch (paramIndex)
                        {
                            case 0:
                                generator.Emit(OpCodes.Ldarg_1);
                                break;

                            case 1:
                                generator.Emit(OpCodes.Ldarg_2);
                                break;

                            case 2:
                                generator.Emit(OpCodes.Ldarg_3);
                                break;

                            default:
                                generator.Emit((paramIndex < 255)
                                    ? OpCodes.Ldarg_S
                                    : OpCodes.Ldarg,
                                    paramIndex + 1);
                                break;
                        }

                    }
                    generator.Emit(OpCodes.Callvirt, method);
                    if (method.ReturnType != typeof(void) && index < services.Length - 1)
                        generator.Emit(OpCodes.Pop); // discard N-1 return values
                }
                generator.Emit(OpCodes.Ret);
            }

            return Activator.CreateInstance(type.CreateType(), services);
        }
    }
}
梦亿 2024-08-06 06:01:35

(我通过添加额外的上下文/信息来证明答案的合理性)

是的,目前 Reflection.Emit 是解决此问题的唯一方法。

在 .NET 4.0 中,Expression 类已扩展为支持循环和语句块,因此对于单一方法使用,编译后的 Expression 将是个好主意。 但即使这样也不支持多方法接口(仅支持单方法委托)。

幸运的是,我以前已经这样做过; 请参阅如何用 C# 编写实现给定接口的通用容器类?

(I'm justifying an answer here by adding extra context/info)

Yes, at the moment Reflection.Emit is the only way to solve this.

In .NET 4.0, the Expression class has been extended to support both loops and statement blocks, so for single method usage, a compiled Expression would be a good idea. But even this won't support multi-method interfaces (just single-method delegates).

Fortunately, I've done this previously; see How can I write a generic container class that implements a given interface in C#?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文