简体   繁体   English

动态返回类型取决于C#中的子类?

[英]Dynamic return type that depends on subclass in C#?

I would like to declare a base class Command with abstract getter GetValue . 我想用抽象的getter GetValue声明一个基类Command Then I subclass this to TalkCommand and SpendCommand for example. 然后,我将其子类TalkCommandSpendCommand

Both class must implement GetValue , however, I want to TalkCommand to return string type for GetValue (what to talk?) and I want the SpendCommand to return int type. 这两个类都必须实现GetValue ,但是,我想让TalkCommand返回GetValue string类型( TalkCommand什么?),我想让SpendCommand返回int类型。 (how much to spend?) (要花多少钱?)

I don't know how to write this in the Command base class. 我不知道如何在Command基类中编写此代码。 Is this design possible in C#? 这种设计在C#中可行吗?

In addition to Manfred's answer, I want to give you a hint to go further. 除了曼弗雷德(Manfred)的答案,我想给你一个提示,进一步说明。

While generics provide you a valuable tool to define GetValue return type, you wouln't be able to perform the following upcast: 虽然泛型为您提供了定义GetValue返回类型的有价值的工具,但您将无法执行以下转换:

abstract class Command<T>
{
    public abstract T GetValue();
}

class TalkCommand : Command<string>
{
    public override string GetValue()
    {
        return "Test";
    }
}

// TalkCommand isn't Command<object>
Command<object> cmd = new TalkCommand();

Instead of defining your command as an abstract class, I would start defining it as an interface with covariance : 与其将命令定义为抽象类,不如将其定义为具有协方差的接口:

public interface ICommand<out T>
{
    T GetValue();
}

And the whole abstract class could implement ICommand<out T> : 整个抽象类都可以实现ICommand<out T>

abstract class Command<T> : ICommand<T>
{
    public abstract T GetValue();
}

Now you can upcast an implementation of ICommand<string> to ICommand<object> : 现在,您可以将ICommand<string>实现ICommand<object>

ICommand<string> cmd = new TalkCommand();
ICommand<object> cmd2 = cmd;

This is good, because you get the best of two worlds: 这很好,因为您获得了两个世界的精华:

  • Your commands have strong return types. 您的命令具有很强的返回类型。
  • Your commands can be generically treated as implementations of ICommand<object> and this let you call GetValue and return object in some situations where you don't know the generic argument to provide or you don't need it at all. 您的命令通常可以视为ICommand<object>实现,在某些情况下您不知道要提供的通用参数或根本不需要它时,可以使用它调用GetValue并返回object

If return type must be different depending on subclass, you would be better off not having this method in the base class. 如果返回类型必须根据子类而有所不同,则最好不要在基类中使用此方法。

The idea behind having a base type in the first place is to let you deal with all subclasses in a uniform way. 首先具有基本类型的想法是让您以统一的方式处理所有子类。 One should be able to write 一个应该能够写

Command cmd = ... // One of subclasses
<some-type> val = cmd.GetValue();

and replace <some-type> with the actual type. 并将<some-type>替换为实际类型。 In your case, however, there is no common base type other than object for int and string , so having a different return type becomes pointless. 但是,在您的情况下,除了intstring object以外,没有其他通用的基本类型,因此拥有不同的返回类型变得毫无意义。

You can implement GetValue as generic, like this: 您可以将GetValue实现为通用,如下所示:

abstract class Command {
    abstract T GetValue<T>();
    ...
}

but that would require the caller to pass the desired type as type parameter of GetValue , which would defeat the purpose of having GetValue in the base class. 但这要求调用者将所需的类型作为GetValue类型参数传递,这将使在基类中具有GetValue的目的GetValue

Possible fix: One approach that lets callers stay away from knowing the type is visitor - make an interface that lets Command "push" the value into the caller, like this: 可能的解决方法一种使调用者不知道类型的方法是访客-创建一个接口,使Command将值“推”到调用者中,如下所示:

IValueGetter {
    void SetInt(int v);
    void SetString(string v);
}

abstract class Command {
    abstract void GetValue(IValueGetter g);
}
class TalkCommand {
    override void GetValue(IValueGetter g) {
        g.SetString("hello");
    }
}
class BuyCommand {
    override void GetValue(IValueGetter g) {
        g.SetInt(123);
    }
}

Now the caller can interact with Command without knowing the type of its GetValue . 现在,调用者可以在不知道其GetValue类型的情况下与Command进行交互。 Commands that produce an int would call back SetInt , while commands that produce string would call SetString . 产生int命令将回调SetInt ,而产生string命令将调用SetString

Only if your base class Command is generic or you don't mind working with object . 仅当您的基类Command是通用的,或者您不介意使用object

Generic Example 通用示例

If you always work with a known instance type, this would be the best approach since you can with the data type you need without casting. 如果您始终使用已知的实例类型,那么这将是最好的方法,因为您可以无需转换就可以使用所需的数据类型。

abstract class Command<T>
{
    public abstract T GetValue();
}

class TalkCommand : Command<string>
{
    public override string GetValue()
    {
        return "Test";
    }
}

class SpendCommand : Command<int>
{
    public override int GetValue()
    {
        return 1234;
    }
}

Object Example 对象实例

This is a very universal approach, pretty much what WPF does with DependencyProperties and non-generic collections like IList or IEnumerable . 这是一种非常通用的方法,几乎​​是WPF对DependencyProperties和非通用集合(如IListIEnumerable所做的工作。

abstract class Command
{
    public abstract object GetValue();
}

class TalkCommand : Command
{
    public override object GetValue()
    {
        return "Test";
    }
}

class SpendCommand : Command
{
    public override object GetValue()
    {
        return 1234;
    }
}

Hybrid Example 混合范例

If you want to be able to use both, you'll need a non-generic base class and inherit another abstract generic class from it. 如果要同时使用两者,则需要一个非泛型基类并从中继承另一个抽象泛型类。

abstract class Command
{
    public abstract object GetValue();
}

abstract class Command<T> : Command
{
    public T GetTypedValue()
    {
        return (T)GetValue();
    }
}   

class TalkCommand : Command<string>
{
    public override object GetValue()
    {
        return "Test";
    }
}

class SpendCommand : Command<int>
{
    public override object GetValue()
    {
        return 1234;
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM