简体   繁体   中英

Dynamic return type that depends on subclass in C#?

I would like to declare a base class Command with abstract getter GetValue . Then I subclass this to TalkCommand and SpendCommand for example.

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. (how much to spend?)

I don't know how to write this in the Command base class. Is this design possible in C#?

In addition to Manfred's answer, I want to give you a hint to go further.

While generics provide you a valuable tool to define GetValue return type, you wouln't be able to perform the following upcast:

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> :

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

Now you can upcast an implementation of ICommand<string> to 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.

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. 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.

You can implement GetValue as generic, like this:

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.

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:

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 . Commands that produce an int would call back SetInt , while commands that produce string would call SetString .

Only if your base class Command is generic or you don't mind working with 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 .

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;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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