我正在尝试设计一种模式来协调几项操作。每个操作都会获取一个参数并传递结果。以下操作可能会或可能不会使用该结果。 这是设计的简化版,但是如果你在控制台上复制/粘贴它,它就会“工作”(有一个编译错误,我无法修复)。
错误
类型 '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.");
}
}
}
}
}
}
答案 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添加到列表中。 我真的不知道该建议什么,因为我不想完全改变你的代码的意图(特别是因为我不能说我完全理解它......) 您可以在此答案中找到有用的内容,例如:
答案 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 强>