通用转换问题

时间:2013-04-23 20:38:35

标签: c# design-patterns generics

我正在尝试设计一种模式来协调几项操作。每个操作都会获取一个参数并传递结果。以下操作可能会或可能不会使用该结果。 这是设计的简化版,但是如果你在控制台上复制/粘贴它,它就会“工作”(有一个编译错误,我无法修复)。

错误

  

类型   'ConsoleApplication1.InternalDebit'   不能在泛型类型或方法中用作类型参数“T1”   'ConsoleApplication1.Orchestrator.Add(T1')。没有隐含的   参考转换自   'ConsoleApplication1.InternalDebit'   至   'ConsoleApplication1.Operation'。 c:\ projects \ BCP \ BaseMvc \ ConsoleApplication1 \ ConsoleApplication1 \ Program.cs 17 13 ConsoleApplication1

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var internalDebit = new InternalDebit<InternalDebitParameter, InterbankCreditParameter>(new InternalDebitParameter() { Id = 1 });

            var orchestrator = new Orchestrator();

            // error here!
            orchestrator.Add(internalDebit);
        }
    }

    public interface IParameter
    {
    }

    public interface IResult
    {
    }

    public interface IReversible
    {
        void Reverse();
    }


    public interface IOperation<T, R>
        where T : class, IParameter
        where R : class, IResult
    {
        Type ParameterType { get; }

        Type ResultType { get; }

        T Parameter { get; set; }

        R Execute(T parameter);
    }

    public abstract class Operation<T, R> : IOperation<T, R>
        where T : class, IParameter
        where R : class, IResult
    {
        public virtual R Execute(T parameter)
        {
            this.Parameter = parameter;
            return default(R);
        }

        public Type ParameterType
        {
            get { return typeof(T); }
        }

        public Type ResultType
        {
            get { return typeof(R); }
        }

        public T Parameter { get; set; }

        public Operation(T parameter)
        {
            this.Parameter = parameter;
        }
    }

    public class InternalDebitParameter : IParameter
    {
        public int Id { get; set; }
    }

    public class InterbankCreditParameter : IParameter, IResult
    {
        public int Id { get; set; }
    }



    public class InternalDebit<T, R> : Operation<T, R>
        where T : class, IParameter
        where R : class, IResult
    {
        public InternalDebit(T parameter)
            : base(parameter)
        {
        }

        public override R Execute(T parameter)
        {
            return new InterbankCreditParameter() { Id = 2 } as R;
        }
    }

    public class Orchestrator
    {
        public List<Operation<IParameter, IResult>> Operations { get; private set; }

        public List<IParameter> Parameters { get; private set; }

        public void Add<T1>(T1 t) where T1 : Operation<IParameter, IResult>
        {
            this.Operations.Add(t);
        }

        public void SetUpParameters(params IParameter[] parameters)
        {
            this.Parameters = new List<IParameter>();

            parameters.ToList().ForEach(s => this.Parameters.Add(s));
        }

        public void Play()
        {
            IParameter generalResult = null;

            foreach (var instrument in this.Operations)
            {
                var parameter = this.Parameters.FirstOrDefault(s => s.GetType() == instrument.ParameterType);

                if (parameter == null)
                {
                    IResult actualResult = null;

                    if (generalResult != null)
                    {
                        try
                        {
                            actualResult = instrument.Execute(generalResult);
                        }
                        catch (Exception ex)
                        {
                            if (instrument is IReversible)
                                ((IReversible)instrument).Reverse();
                            else
                                throw;
                            break;
                        }
                        finally
                        {
                            if (actualResult is IParameter)
                                generalResult = (IParameter)actualResult;
                        }
                    }
                    else
                    {
                        throw new Exception("Orchetrator missconfiguration.");
                    }
                }
            }
        }
    }
}

3 个答案:

答案 0 :(得分:1)

你正在将泛型带入C ++的模板能力。在给出错误的行上,您隐式创建了函数:

 public void Add(InternalDebit<InternalDebitParameter, InterbankCreditParameter>);

如声明,此类继承自:

 Operation<InternalDebitParameter, InterbankCreditParameter>

通用要求Howeveer声明T1应该是Operation<IParameter, IResult>类型,它不是,即使两个参数都从正确的类型继承,因为没有允许多态。

你在这里想要实现的是泛型(或实际上是C ++中的模板)本身是不可能的,因为你指定的方式太多了,并且指定了永远不能满足的继承要求。你需要记住,泛型只是一种奢侈的简写,只用一点点代码编写许多类,它们不会突然引入递归多态。

长话短说,重写代码以使用继承和基类而不是依赖于泛型。我怀疑你的整个模式是可能的,没有一个通用的,只是类型安全。

答案 1 :(得分:1)

如果你在协方差/逆变中玩一点点,你可以做一些类似于你所追求的事情。或者无论如何,编译器会更准确地告诉你你要做的事情不是类型安全的。

第一步:您获得的错误表明There is no implicit reference conversion from 'InternalDebit<InternalDebitParameter,InterbankCreditParameter>' to 'Operation<IParameter,IResult>'

因此,由于InternalDebit实现IOperation,您可以做的第一件事是使IOperation协变,尝试将其定义为:

public interface IOperation<out T, out R>

这意味着IOperation<IParameter,IResult>类型的变量会很乐意接受Operation<InternalDebitParameter,InterbankCreditParameter>类型的值,这比您想要的更接近一步。 然后,您的Add方法签名将以IOperation而非Operation

的形式受到约束
public void Add<T1>(T1 t) where T1 : IOperation<IParameter, IResult>

编译器告诉我们一些错误:

Invalid variance: The type parameter 'T' must be invariantly valid on 'IOperation<T,R>.Parameter'. 'T' is covariant.

Invalid variance: The type parameter 'T' must be contravariantly valid on 'IOperation<T,R>.Execute(T)'. 'T' is covariant.

这是该代码为何不合理的第一个迹象。协变参数只能在“出路”功能中使用(即作为返回类型),而不能用作“参数”。

第二步使IOperation协变。这可能会很痛苦并且会更改您的代码,因为这意味着更改Execute不接受T类型的参数。

public interface IOperation<out T, out R>
    where T : class, IParameter
    where R : class, IResult
{
    Type ParameterType { get; }

    Type ResultType { get; }

    T Parameter { get; /*set;*/ } //can't allow the interface to set T 

    // R Execute(T parameter); // can't have an Execute with T as a parameter
    R Execute(); // you can however inject T in the constructor of the
                 // inherited class and call Execute without parameters    
}

第三步您现在收到一个新错误:

The best overloaded method match for 'System.Collections.Generic.List<Operation<IParameter,IResult>>.Add(Operation<IParameter,IResult>)' has some invalid arguments

这又是一个协方差问题。列表不是协变的,您不能将t添加到列表中。 我真的不知道该建议什么,因为我不想完全改变你的代码的意图(特别是因为我不能说我完全理解它......) 您可以在此答案中找到有用的内容,例如:

Covariance and IList

答案 2 :(得分:1)

好的,为了这篇文章的完整性,我会告诉你我最终是如何工作的。 它可以更好,我仍然愿意接受建议。不幸的是,我不得不继续这项任务,已经推迟了。

我会发布并修改此答案,以便在 Code Review 网站上进行跟进。

在控制台应用程序中复制/粘贴,它是一个功能齐全的代码示例。

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

            var orchestrator = new Orchestrator(new InternalDebitOperation(transferenceInfo),
                                                new InterbankCreditOperation(),
                                                new CommissionOperation());

            orchestrator.Run();
        }
    }

    public class InterbankTranferenceInfo : IParameter
    {
        public bool InternalDebitDone { get; set; }

        public bool InterbankCreditDone { get; set; }

        public bool CommissionDone { get; set; }
    }

    public class InternalDebitOperation : Operation<InterbankTranferenceInfo>, IOperation<InterbankTranferenceInfo>
    {
        public InternalDebitOperation(InterbankTranferenceInfo parameter)
            : base(parameter)
        {
        }

        public override InterbankTranferenceInfo Execute()
        {
            return new InterbankTranferenceInfo() { InternalDebitDone = true };
        }
    }

    public class InterbankCreditOperation : Operation<InterbankTranferenceInfo>, IOperation<InterbankTranferenceInfo>
    {
        public override InterbankTranferenceInfo Execute()
        {
            Parameter.InterbankCreditDone = true;

            return Parameter;
        }
    }

    public class CommissionOperation : Operation<InterbankTranferenceInfo>, IReversible, IOperation<InterbankTranferenceInfo>
    {
        public override InterbankTranferenceInfo Execute()
        {
            Parameter.CommissionDone = true;

            // Uncomment this code to test Reverse operation.
            // throw new Exception("Test exception, it should trigger Reverse() method.");

            return Parameter;
        }

        public void Reverse()
        {
            Parameter.CommissionDone = false;
        }
    }

    public enum OperationStatus
    {
        Done,
        Pending,
        Reversed
    }

    public interface IParameter
    {
    }

    public interface IReversible
    {
        void Reverse();
    }

    public interface IOperation<out T> : IInternalOperation<T>  where T : IParameter
    {
    }

    public interface IInternalOperation<out T> : IExecutableOperation<T>
    {
        bool GetParameterFromParentOperation { get; }

        OperationStatus Status { get; set; }

        IParameter Execute(IParameter parameter);      
    }

    public interface IExecutableOperation<out T>
    {
        T Execute();
    }

    //[System.Diagnostics.DebuggerStepThroughAttribute()]
    public abstract class Operation<T> : IInternalOperation<T> where T : IParameter
    {
        public T Parameter { get; private set; }

        public bool GetParameterFromParentOperation { get { return this.Parameter == null; } }

        public OperationStatus Status { get; set; }

        public Operation()
        {
            Status = OperationStatus.Pending;
        }

        public Operation(IParameter parameter)
        {
            Status = OperationStatus.Pending;
            this.Parameter = (T)parameter;
        }

        public abstract T Execute();

        public virtual IParameter Execute(IParameter parameter)
        {
            this.Parameter = (T)parameter;
            return this.Execute();
        }
    }

    public class Orchestrator
    {
        public List<IOperation<IParameter>> Operations { get; private set; }

        public Orchestrator(params IOperation<IParameter>[] operations) 
        {
            this.Operations = new List<IOperation<IParameter>>();

            foreach (var item in operations)
            {
                this.Operations.Add((IOperation<IParameter>)item);
            }
        }

        public IParameter Run()
        {
            IParameter previousOperationResult = null;

            foreach (var operation in this.Operations)
            {
                try
                {
                    if (operation.GetParameterFromParentOperation)
                        previousOperationResult = operation.Execute(previousOperationResult);
                    else
                        previousOperationResult = operation.Execute();

                    operation.Status = OperationStatus.Done;
                }
                catch (Exception)
                {
                    foreach (var o in this.Operations)
                    {
                        if (o is IReversible)
                        {
                            ((IReversible)o).Reverse();
                            o.Status = OperationStatus.Reversed;
                        }
                        else
                            throw;
                    }
                    break;
                }
            }

            return previousOperationResult;
        }
    }

修改

<强> Code Review Post