设计多态而无需向下转换

时间:2017-07-26 15:50:01

标签: c# generics polymorphism

我有一个适用于硬件设备的课程。该设备支持许多命令,我想实现一个通用的 SendCommand 功能。命令可能有也可能没有输入参数和/或输出结果。

我能做的是编写一个抽象的命令类型类和一些派生的命令类型类。那些派生类实际上与命令的输入/输出细节不同。

现在我希望SendCommand返回Task<SpecificCommandType>,即派生类的任务,但使用当前设计我只能返回Task<BaseCommandType>

我将用简单的骨架代码解释:

类:

public abstract class BaseCommandType { ... }

public class CommandType1 : BaseCommandType {
    TaskCompletionSource<CommandType1> Tcs;
}

public class CommandType2 : BaseCommandType {
    TaskCompletionSource<CommandType2> Tcs;
}

我的功能:

public Task<T> SendCommand<T>(BaseCommandType type) where T : BaseCommandType {
    ...
    // if I implement TaskCompletionSource<BaseCommandType> Tcs
    // in abstract class, then I can return type.Tcs.Task, and remove
    // generics.
    // But how can I return Task<T>? 
}

我打算像这样使用这个功能:

CommandTypeX cmd = new CommandTypeX(...);
SendCommand<CommandTypeX>(cmd).ContinueWith(t => {
    // access some specifics of t.Result as CommandTypeX
});

我应该如何设计我的课程以便能够返回Task<CommandTypeX>

还是有更好的方法来做我需要的事情(没有垂头丧气)?

UPDATE1 : 更准确地说,我可以这样做,同时投降(可以做我,不是吗?):

public abstract class BaseCommandType {
    public TaskCompletionSource<BaseCommandType> Tcs;
}

public class CommandTypeX : BaseCommandType { }

public Task<BaseCommandType> SendCommand(BaseCommandType type) {
    ...
    return type.Tcs.Task;
}

// when task finishes:
type.Tcs.SetResult(type); // where type is actually of CommandTypeX

// usage:
CommandTypeX cmd = new CommandTypeX(...);
SendCommand(cmd).ContinueWith(t => {
    CommandTypeX command = t.Result as CommandTypeX;
    if (command != null) ...
});

但这正是我想要首先避免的。

UPDATE2 : 我想我找到了另一种方式,但对我来说仍然不太好。

public abstract class BaseCommandType {
    internal abstract void SetTcs<T>(TaskCompletionSource<T> tcs);
    internal abstract void HandleData(byte[] data);
}

public class CommandType1 : BaseCommandType {
    private TaskCompletionSource<CommandType1> _tcs1 = new TaskCompletionSource<CommandType1>();
    public string Data1;

    internal override void SetTcs<T>(TaskCompletionSource<T> tcs)
    {
        _tcs1 = tcs as TaskCompletionSource<CommandType1>;
    }

    internal override void HandleData(byte[] data)
    {
        // Data1 = someFuncOn(data)
        _tcs1.TrySetResult(this);
    }
}

public class CommandType2 : BaseCommandType {
    private TaskCompletionSource<CommandType2> _tcs2 = new TaskCompletionSource<CommandType2>();
    public int[] Data2;

    internal override void SetTcs<T>(TaskCompletionSource<T> tcs)
    {
        _tcs2 = tcs as TaskCompletionSource<CommandType2>;
    }

    internal override void HandleData(byte[] data)
    {
        // Data2 = someFuncOn(data)
        _tcs2.TrySetResult(this);
    }
}

public class Device {
    private List<BaseCommandType> _commandList = new List<BaseCommandType>();

    public Task<T> SendCommand<T>(T t) where T : BaseCommandType
    {
        TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
        t.SetTcs<T>(tcs);
        _commandList.Add(t);

        // later in other thread then device answers
        // locate command in list
        // BaseCommandType c = _commandList[some index];
        // _commandList.RemoveAt(some index);
        // c.HandleData(null);

        return tcs.Task;
    }
}

// usage like this:
CommandType2 command = new CommandType2();
device.SendCommand<CommandType2>(command).ContinueWith(t =>
        {
            CommandType2 command2 = t.Result;
            // use command2.Data2 here;
        });

这比update1更好吗?至少我可以隐藏库中的构建逻辑,所以外部的一切都是类型安全和健壮的。 或者我该如何进一步改进呢?

2 个答案:

答案 0 :(得分:0)

我不确定我是否理解它,但是......

public abstract class BaseCommandType<T>
{
    public abstract TaskCompletionSource<T> Tcs { get; }
}
public class CommandType1 : BaseCommandType<CommandType1>
{
}
public class CommandType2 : BaseCommandType<CommandType2>
{
}


public Task<T> SendCommand<T>(T type) where T : BaseCommandType<T>
{
    return type.Tcs.Task;
}

修改

如果你不能拥有通用输入参数,那么在BaseCommandType上定义抽象beahvior而不是向下转换定义,你可以在ContinueWith方法中调用它,并在命令中覆盖它。如果您不了解输入类型,则无法将其设为通用。

答案 1 :(得分:0)

我对你的问题不了解的事情:

  1. 为什么限制SendCommand<T>(BaseCommandType)的参数类型?如果你改为使用SendCommand<T>(T)方法,哪些方法不起作用?
  2. 您为什么使用ContinueWith()await会更容易,更具表现力。
  3. 暂时忽略这些,我希望你能做到这样的事情:

    public async Task<T> SendCommand<T>(BaseCommandType type) where T : BaseCommandType
    {
        return await type.Tcs.Task as T;
    }
    

    就个人而言,我会全力以赴。但是你说你不能出于任何原因,所以你必须在某个时候施展。使用该约束不可能简单地避免铸造。以上似乎是最简单,最有用的方法。

    如果以上内容无法满足您的需求,请通过添加一个显示所有设计限制和要求的好Minimal, Complete, and Verifiable code example来改进问题,您尝试了什么,并解释您尝试过的原因不符合你的目标。