如何模拟从泛型参数继承?

时间:2015-08-18 14:58:26

标签: c# generics inheritance

实际上从泛型参数继承的实际上已经多次覆盖了。我的问题是,实际上,如何才能达到类似的效果?使用案例:

public interface ILotsOfMethods
{ 
    // Lots of methods go here
}

public class LotsOfMethods<T> : T, ILotsOfMethods
{
    public Func<string, bool> Method1Delegate { get; set; }
    public Func<string, bool> Method2Delegate { get; set; }
    // ...
    public Func<string, bool> MethodNDelegate { get; set; }

    public bool ILotsOfMethods.Method1(string str)
    {
        if (this.Method1Delegate != null)
        {
            return this.Method1Delegate(str);
        }

        throw new NotImplementedException();
    }

    // the other methods all follow this pattern
}

public class LotsOfMethodsList : List<ILotsOfMethods>, ILotsOfMethods
{
    public bool ILotsOfMethods.Method1(string str)
    {
        foreach (var handler in this)
        {
            try
            {
                return handler.Method1(str); // even if defined, this COULD throw NotImplementedException if it decides it's not interested in str
            }
            catch(NotImplementedException)
            { }
        }

        throw new NotImplementedException();
    }

    // the other methods all follow this pattern
}

通过这种设置,我可以通过编写来轻松实现我感兴趣的方法的一部分:

public class MethodAwareClient : ThirdPartyClient, ILotsOfMethods
{
    // concrete implementations of ILotsOfMethods as default handlers

    public void override Exec(Query query)
    {
        Log.Trace("Executing!");  // could be something more meaningful
        base.Exec(query); // third party code
    }

    public void override QueryComplete(Query query)
    {
        LotsOfMethodsList handlers;
        handlers.Add(this);
        handlers.Add(query as ILotsOfMethods); //null checking ommitted for brevity

        if (handlers.Method1("needsApproval"))
        {
            throw new BigBadException(); // Don't judge the logic of throwing after the query completes too harshly...simply for example!
        }
    }
}

public void Foo()
{
    Query normalQuery;
    LotsOfMethods<Query> scaryQuery;
    scaryQuery.Method1Handler = (string str) => { return true; };

    MethodAwareClient mac;
    mac.Exec(normalQuery);
    mac.Exec(scaryQuery); // this should throw
}

一般目标是允许任何类型的任何对象轻松地基于每个实例覆盖特定任务,而不必为每个想要实现某些功能的类生成ILotsOfMethods的潜在大型实现。

更具体的示例:想象一个类MySqlClient,它实现IQueryApproval的基本处理程序,并从第三方SqlClient继承。此界面将包含IQueryApproval.NeedsApproval(Query)IQueryApproval.GetApprovers(Query)。默认值可能是更新需要批准,而选择则不需要。但是如果我正在编写一个我认为很危险的查询(无论是从PII的角度还是性能),也许我想要覆盖这种行为。所以我将QueryApproval<Query>传递给MySqlClient,它由SqlClient以各种方式传递给我。通过继承Query,即使MySqlClient覆盖方法CanRunQuery(Query query),我们也可以维护有关Query的上下文信息,同时保持SqlClient定义的底层接口。

我可以通过将IQueryApproval分解为INeedsApprovalIGetApprovers,并为每个实现委托包装器来了解如何执行此操作。我还可以通过在每个派生类中完全实现ILotsOfMethods来了解如何执行此操作,但这两个似乎都有点笨拙。我承认我的上面也有点开销,但是如果实现了接口的用户将简化其代码,这实际上是目标。

3 个答案:

答案 0 :(得分:0)

听起来您希望能够为以前定义的类添加功能。

尝试使用Castle.DynamicProxy2创建mixins。这是关于执行http://kozmic.net/2009/08/12/castle-dynamic-proxy-tutorial-part-xiii-mix-in-this-mix/

的写作的链接

一般的想法类似于创建装饰器,除非不必创建自己的转发方法。

据推测,根据教程你可以这样做:

public static TBaseType CreateMixin<TBaseType, TAddon>(Func<TAddon> constructAddon) where TBaseType : class where TAddon : class
{
    var generator = new ProxyGenerator();
    var options = new ProxyGenerationOptions();
    options.AddMixinInstance(constructAddon());
    return (TBaseType)generator.CreateClassProxy(typeof(TBaseType), new Type[]{typeof(TAddon)}, options);
}

此方法返回的内容实际上是TBaseType的子类,它还实现TAddon,然后将该接口的调用转发给在constructAddon工厂方法中创建的实例。

这样称呼:

public void Foo()
{
    Form normalForm = CreateMixin<Form, ILotsOfMethods>(()=>new LotsOfMethods());
    ILotsOfMethods wrappedForm = (ILotsOfMethods) normalForm;
    wrappedForm.Method1Handler = (string str) => { return true; };

    Database normalDatabase = CreateMixin<Database, ILotsOfMethods>(()=>new LotsOfMethods());
    ILotsOfMethods wrappedDatabase = (ILotsOfMethods) normalDatabase;
    wrappedDatabase.Method3Handler = (string str) => { return false; };

    MethodAwareClient mac;
    mac.ProcessForm(wrappedForm);
    mac.ProcessDatabase(wrappedDatabase);
}

请注意,这只会添加到非密封类。但是,库提供了其他选项,如果它们实现了可以使用的接口,您仍然可以使用密封类。然后,您可以创建一个实现目标类的主接口和附加接口的代理,代理会将调用转发给主接口的目标类实例和附加类的实例对于附加方法。

这是一个link(http://www.somethingorothersoft.com/2010/08/12/type-safe-dynamic-mixins/),显示基于它们实现的接口混合三个类,合并到一个复合接口。请注意,使用此技术,您可以合并多个接口,这可以允许您将ILotsOfMethods分解为多个IAFewRelatedMethod接口。

答案 1 :(得分:0)

首先,我怀疑这可能是XY Problem的情况。如果是,可能包括您尝试做的更完整的背景。假设不是,那么......

其次,我担心使用大ILotsOfMethods;这可能是Interface Segregation Principle的违反者,也许是你头痛的根本原因。我认为这应该是你重新设计的第一个考虑途径。假设这个界面设计是固定/最适合你的,那么......

根据您的评论,这可能是基于Decorator pattern的可行设计。

首先,定义基本接口和装饰器:

public interface ILotsOfMethods
{ 
    // Lots of methods go here
    bool Method1(string arg);
    bool Method2(string arg);
    TimeSpan Method6(string arg);
}

public abstract class LotsOfMethodsDecorator : ILotsOfMethods
{
    private readonly ILotsOfMethods LotsOfMethodsToBeDecorated;

    protected LotsOfMethodsDecorator(ILotsOfMethods lotsOfMethods)
    {
        this.LotsOfMethodsToBeDecorated = lotsOfMethods;
    }

    public virtual bool Method1(string arg)
    {
        return LotsOfMethodsToBeDecorated.Method1(arg);
    }

    public virtual bool Method2(string arg)
    {
        return LotsOfMethodsToBeDecorated.Method2(arg);
    }

    public virtual TimeSpan Method6(string arg)
    {
        return LotsOfMethodsToBeDecorated.Method6(arg);
    }
}

然后是一个常见的终止点,它会为每个方法抛出NotImplementedException

public class LotsOfMethodsNotImplemented : ILotsOfMethods
{
    public bool Method1(string arg)
    {
        throw new NotImplementedException();
    }

    public bool Method2(string arg)
    {
        throw new NotImplementedException();
    }

    public TimeSpan Method6(string arg)
    {
        throw new NotImplementedException();
    }
}

方法感知客户端现在可以简单地覆盖他们知道如何实现的方法:

public class Method1Client : LotsOfMethodsDecorator
{
    public Method1Client(ILotsOfMethods lotsOfMethods)
        : base(lotsOfMethods)
    {

    }

    public override bool Method1(string arg)
    {
        return  arg == "shouldContinue";
    }
}

不能从基础装饰器继承的第三方组件应该用基本装饰器包装,并且可以执行第三方特定代码:

public class Some3rdPartQueryWrapper : LotsOfMethodsDecorator
{
    private readonly Some3rdPartyQuery Query;

    public Some3rdPartQueryWrapper(Some3rdPartyQuery query, ILotsOfMethods lotsOfMethods)
        : base(lotsOfMethods)
    {
        this.Query = query;
    }

    public override bool Method2(string arg)
    {
        if (this.Query.MaybeSomeThirdPartyValidation())
            return false;
        else
            return this.Query.SomeThirdPartyMethod2(arg);
    }
}

现在,我对您实施ILotsOfMethods的高级“MethodAwareClient”感到困惑。我将假设这也是这个答案的装饰器,但您可以轻松地将其更改为简单地包装装饰器或理想情况下只是ILotsOfMethods实例:

public class MethodAwareClient : LotsOfMethodsDecorator
{
    public MethodAwareClient(ILotsOfMethods lotsOfMethods)
        : base(lotsOfMethods)
    {

    }

    public void HandleForm()
    {
        if (Method1("shouldContinue"))
        {
            //...
        }

        if (Method2("needSleep"))
        {
            Task.Delay(Method6("sleepTime"));
        }
    }
}

一起布线:

var some3rdPartyQuery = new Some3rdPartyQuery();

var client = 
    new MethodAwareClient(
    new Some3rdPartQueryWrapper(some3rdPartyQuery, 
    new Method1Client(
    new LotsOfMethodsNotImplemented()
    )));

client.HandleForm();

嵌套可能会变得毛茸茸,但是一些API调整可以解决这个问题。

同样,我怀疑这不是您面临的具体问题的理想解决方案。我强烈建议您首先考虑我最初的两个问题(XY问题和接口隔离原则)。

答案 2 :(得分:0)

我遇到了类似的问题。 我有一个我不能指望在运行时可用的程序集,但如果它存在,我希望能够在其中使用对象。

问题在于我不允许在我自己内部引用程序集。

让我们说我想要的对象是这样的:

public class RunTimeClass {
    public RunTimeClass () { }
    public void DoSomethingCool() { /*...*/ }
    public string SomeProp { get; set; }
}

我所做的是定义与此类匹配的接口:

public class ICompileTime {
   void DoSomethingCool();
   string SomeProp { get; set; }
}

然后我编写了一些给定接口或抽象类的代码,一个程序集和类的名称将动态编写一个实现接口的新类,并将所有方法重新路由到程序集中的目标类。实际上,这使得动态适配器成为RunTimeClass

用法:

Assembly asm = Assembly.Load("DoNothing");
Type t = asm.GetType("DoNothing.Nothing");

AdapterCompiler compiler = new AdapterCompiler();
AdapterFactory<NothingAdapter> factory = compiler.DefineAdapter<NothingAdapter>(t);

NothingAdapter adapter = factory.Construct(new object[] { "dave" });

int sum = adapter.Sum(1, 2, 3);

我要继续把整个shebang放在这里 - 它很长,但不是超长。首先,AdapterCompiler:

public class AdapterCompiler
{
    public AdapterFactory<T> DefineAdapter<T>(Type targetType)
    {
        return DefineAdapter<T>(targetType, null);
    }

    public AdapterFactory<T> DefineAdapter<T>(Type targetType, string outputFile)
    {
        Type abstractType = typeof(T);
        AssemblyBuilder ab = null;

        // Get the TypeBuilder for the new class
        TypeBuilder tb = GetTypeBuilder(abstractType, out ab, outputFile);

        // Make a field for the target type within the new class
        FieldBuilder fb = DefineTargetObjectField(tb, targetType);

        // Map the abstract methods onto the target type
        DefineAbstractMethods(tb, fb, abstractType, targetType);

        // make a constructor that can vector out to the target type
        DefineConstructor(tb, fb, abstractType, targetType);

        // build the class
        Type adaptedType = tb.CreateType();

        if (outputFile != null)
            ab.Save(outputFile);

        // Make a factory object for building the class
        return new AdapterFactory<T>(adaptedType, targetType);
    }

    private string GetTargetObjectFieldName(Type targetType)
    {
        // create a consistent name mangled field name
        return "_f" + targetType.Name;
    }

    private FieldBuilder DefineTargetObjectField(TypeBuilder tb, Type targetType)
    {
        // Define the actual field
        return tb.DefineField(GetTargetObjectFieldName(targetType), targetType, FieldAttributes.Private);
    }

    private TypeBuilder GetTypeBuilder(Type abstractType, out AssemblyBuilder assemblyBuilder, string outputFile)
    {
        // make an assembly builder, module builder and type builder.
        // we only need the AssemblyBuilder and TypeBuilder
        AssemblyName assemName = new AssemblyName("Assembly" + abstractType.Name);
        assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemName,
            outputFile != null ? AssemblyBuilderAccess.RunAndSave : AssemblyBuilderAccess.Run);
        ModuleBuilder mb = outputFile != null ?
            assemblyBuilder.DefineDynamicModule(assemName.Name, outputFile) :
            assemblyBuilder.DefineDynamicModule(assemName.Name);
        TypeBuilder tb = mb.DefineType("Wrapped"+abstractType.Name, abstractType.Attributes & ~(TypeAttributes.Abstract), abstractType);
        return tb;
    }

    private void DefineAbstractMethods(TypeBuilder tb, FieldBuilder fb, Type abstractType, Type targetType)
    {
        // we need to pass along a list of already defined fields
        List<string> definedProperties = new List<string>();

        // get the abstract methods - tricky - use a lambda expression to filter via LINQ
        IEnumerable<MethodInfo> methods = abstractType.GetMethods().Where(mi => mi.IsAbstract);

        foreach (MethodInfo mi in methods)
        {
            DefineAbstractMethod(tb, fb, mi, targetType, definedProperties);
        }
    }

    private Type[] GetParameterTypes(MethodInfo mi)
    {
        // given a method, get a list of the types of its parameters
        ParameterInfo[] pi = mi.GetParameters();
        Type[] types = new Type[pi.Length];

        for (int i = 0; i < pi.Length; i++)
        {
            types[i] = pi[i].ParameterType;
        }
        return types;
    }

    private Type[] GetParameterTypes(PropertyInfo propi)
    {
        // given a property, get a list of the types of its parameters
        ParameterInfo[] pi = propi.GetIndexParameters();
        Type[] types = new Type[pi.Length];
        for (int i = 0; i < pi.Length; i++)
        {
            types[i] = pi[i].ParameterType;
        }
        return types;
    }


    private void VerifyParameterMatch(string methodName, Type[] source, Type[] dest)
    {
        // type match parameters for one method to another
        if (source.Length != dest.Length)
        {
            throw new Exception("In method " + methodName + ", parameter list length for wrapper and target object differ.");
        }

        for (int i = 0; i < source.Length; i++)
        {
            if (source[i] != dest[i])
            {
                throw new Exception("In method " + methodName + ", expected parameter " + i + " to be type " + dest[i].Name + ", but found " +
                    source[i].Name + ".");
            }
        }
    }

    private void DefineAbstractMethod(TypeBuilder tb, FieldBuilder fb, MethodInfo mi, Type targetType, List<string> definedProperties)
    {
        // map the abstract method from one class into the concrete implementation in another

        Type[] parameterTypes = GetParameterTypes(mi);

        // get the target method
        MethodInfo targetMethod = targetType.GetMethod(mi.Name, parameterTypes);

        if (targetMethod == null)
            throw new Exception("Unable to find matching method for " + mi.Name + " in class " + targetType.Name);

        // get the target method's parameter types
        Type[] targetParameterTypes = GetParameterTypes(targetMethod);

        // Ensure that they match, throw on failure
        VerifyParameterMatch(mi.Name, parameterTypes, targetParameterTypes);

        MethodAttributes attrs = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.ReuseSlot;
        // IsSpecialName is equivalent to "implementation of a property"
        if (mi.IsSpecialName)
        {
            attrs = attrs | MethodAttributes.SpecialName;
            // we only really handle get_/set_ properties
            if (mi.Name.StartsWith("get_") || mi.Name.StartsWith("set_"))
            {
                string propName = mi.Name.Substring(4);
                if (!definedProperties.Contains(propName))
                {
                    definedProperties.Add(propName);
                    PropertyInfo pi = mi.DeclaringType.GetProperty(propName);
                    tb.DefineProperty(propName, pi.Attributes, pi.PropertyType, GetParameterTypes(pi));
                }
            }
        }

        // define the method
        MethodBuilder mb = tb.DefineMethod(mi.Name, attrs, mi.ReturnType, parameterTypes);

        ILGenerator gen = mb.GetILGenerator();
        // fetch the target object
        gen.Emit(OpCodes.Ldarg_0);
        gen.Emit(OpCodes.Ldfld, fb);

        // pass all of our parameters on
        for (int i = 0; i < parameterTypes.Length; i++)
        {
            gen.Emit(OpCodes.Ldarg, i + 1);
        }

        // call the method, virtually or otherwise
        gen.Emit(targetMethod.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, targetMethod);
        gen.Emit(OpCodes.Ret);
    }

    private void DefineConstructor(TypeBuilder tb, FieldBuilder fb, Type abstractType, Type targetType)
    {
        // define a construct of the form:
        // .ctor(Type targetObjectType, Type[] constructorArgumentTypes, object[] constructorArguments)
        //
        // This implementation is effectively:
        // ConstructorInfo ci = targetObjectType.GetConstructor(constructorArgumentTypes)
        // _ftargetType = ci.Invoke(constructorArguments)
        //

        ConstructorBuilder cb = tb.DefineConstructor(MethodAttributes.Public,
            CallingConventions.Standard,
            new Type[] { typeof(Type), typeof(Type[]), typeof(object[]) });
        ILGenerator gen = cb.GetILGenerator();

        // see if there is a default constructor in the abstract type
        ConstructorInfo ci = abstractType.GetConstructor(Type.EmptyTypes);
        if (ci == null)
        {
            // if not, get Object contructor
            Type objectType = typeof(object);
            ci = objectType.GetConstructor(Type.EmptyTypes);
        }
        // call base class constructor
        gen.Emit(OpCodes.Ldarg_0);
        gen.Emit(OpCodes.Call, ci);

        // needed later
        gen.Emit(OpCodes.Ldarg_0);

        // get the constructor from targetObjectType
        gen.Emit(OpCodes.Ldarg_1);
        gen.Emit(OpCodes.Ldarg_2); 
        MethodInfo mi = typeof(Type).GetMethod("GetConstructor", new Type[] { typeof(Type[]) });
        gen.Emit(OpCodes.Callvirt, mi);

        // stack is now:
        // 0: this
        // 1: constuctor info

        // invoke the constructor on the arguments
        gen.Emit(OpCodes.Ldarg_3);
        mi = typeof(ConstructorInfo).GetMethod("Invoke", new Type[] { typeof(object[]) });

        gen.Emit(OpCodes.Callvirt, mi);

        // store into _ftargetType
        gen.Emit(OpCodes.Stfld, fb);
        gen.Emit(OpCodes.Ret);
    }
}

接下来是AdapterFactory:

// An AdapterFactory is a class that given a type T, will construct a derived class (generated by AdapterCompiler)
// and call its constructor
//
public class AdapterFactory<T>
{
    private Type _adaptedType, _targetType;

    public AdapterFactory(Type adaptedType, Type targetType)
    {
        _adaptedType = adaptedType;
        _targetType = targetType;
    }

    public T Construct(object[] arguments)
    {
        // get the constructor
        ConstructorInfo ci = _adaptedType.GetConstructor(new Type[] { typeof(Type), typeof(Type[]), typeof(object[]) });

        // invoke it
        Type[] argTypes = GetArgTypes(arguments);
        return (T)ci.Invoke(new object[] { _targetType, argTypes, arguments });
    }

    private Type[] GetArgTypes(object[] objs)
    {
        Type[] types = new Type[objs.Length];
        for (int i = 0; i < types.Length; i++)
        {
            types[i] = objs[i].GetType();
        }
        return types;
    }
}