如何在C#中使用派生类型覆盖通用方法

4

我有以下类:

public interface IService
{
    void ApplyChanges<T>(T parameters) where T : ParamBase;
}

public class ServiceBase : IService
{
    public virtual void ApplyChanges<T>(T parameters) where T : ParamBase
    { }
}
public abstract class Service : ServiceBase
{
    public override void ApplyChanges<T>(T parameters) where T : ParamBase
    {
        Console.WriteLine(parameters.Param2);
        //Apply changes logic here...
    }
}

public abstract class ParamBase
{
    public string Param1 { get; set; }
}

public class ParamA : ParamBase
{
    public string Param2 { get; set; }
}

这是我的测试主类:

void Main()
{
  var service = new Service();
  var paramA = new ParamA();
  paramA.Param2 = "Test2";
  service.ApplyChanges<ParamA>(paramA);
}

这个实现有什么问题?我如何在我的Service类中重写的ApplyChanges方法中访问parameters.Param2?

总的来说,我有一个ServiceBase,我希望它的派生类能够将不同的参数类型传递给ApplyChanges方法。


你为什么有一个不执行任何操作的ApplyChanges实现?这只是在示例中吗? - Domysee
@Domysee 是的,这只是我真实应用程序的简化版本。我只想弄清楚通用继承和类型转换。 - Adolfo Perez
除非你将其声明为抽象的,否则你不能这样做。 - Mattias Nordqvist
Param2 不是 ParamBase 的一部分,因此泛型 T:where ParamBase 对于 Param2 一无所知。 - freedomn-m
@BenjaminHodgson,大致的想法是我有一个ServiceBase类,并且我希望它的派生类可以将不同的参数类型传递给ApplyChanges方法。 - Adolfo Perez
显示剩余2条评论
3个回答

7
我在这里进行了推断,但听起来你打算有多个“服务”,每个服务都有一个相关的参数类型。
像你在示例中所做的那样,在方法上放置类型参数会强制该方法的所有实现具有多态性。(技术术语称为higher-rank quantification。)
相反,你应该将类型参数与服务本身关联。这允许合同的给定实现声明其关联的参数类型。顺便说一下,我不会费心去处理基类或类型边界。
interface IService<in T>
{
    void ApplyChanges(T param);
}

class Param1
{
    public int X { get; set; }
}
class Service1 : IService<Param1>
{
    public void ApplyChanges(Param1 param)
    {
        param.X = 123;
    }
}

class Param2
{
    public int Y { get; set; }
}
class Service2 : IService<Param2>
{
    public void ApplyChanges(Param2 param)
    {
        param.Y = 456;
    }
}

5
你不应该对方法重写施加更强的限制。重写的方法应该扩展可能的输入参数并减少可能的结果。否则,它会违反Liskov Substitution Principle。C#不允许你这样做。
话虽如此,如果你真的想这么做,你可以。但是在调用代码中,你将不会得到编译器警告。如果你不能改变基类,请使用该解决方案。
public class Service<TParam> : Service where TParam : ParamA
{
    public override void ApplyChanges<T>(T parameters)
    {
        Console.WriteLine((parameters as TParam).Param2);
    }
}

更好的解决方案是在 ServiceBaseIService 中添加一个类型参数。
public interface IService<TParam>
   where TParam : ParamBase
{
    void ApplyChanges(TParam parameters);
}

public abstract class ServiceBase<TParam> : IService<TParam>
    where TParam : ParamBase
{
    public virtual void ApplyChanges(TParam parameters)
    { }
}
public class Service : ServiceBase<ParamA>
{
    public override void ApplyChanges(ParamA parameters)
    {
        Console.WriteLine(parameters.Param2);
    }
}

我在执行此操作时遇到了以下错误:重写和显式接口实现方法的约束从基本方法继承,因此不能直接指定。 - Adolfo Perez
我同意我的示例看起来像是代码异味,我不想像你建议的那样强制转换。还有哪些优雅的选项,让我可以拥有一个基础方法,可以传递不同的派生参数类型? - Adolfo Perez
谢谢你的建议,这样好多了。我得公平一点,把答案给BenjaminHodgson,因为他首先发布了一个非常相似的例子。 - Adolfo Perez

0

实际上,与其替换接口的通用类型,使用“类型保护”更加清晰。我之所以这么说是因为接口的方法签名保持一致,而且,什么比接口的使用方式更重要呢?(显然小狗更重要)

在方法内部,您可以确保类型是所需的类型,如下所示...

public void Method(ParentRequest req){
if(req is ChildRequest request){
//Do logic here
} else {
    throw new Exception($"request is of type {req.GetType().Name} and must be of type ParentRequest");
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接