通用方法是否可以根据其类型参数使用不同的(通用)对象?

时间:2019-05-04 23:07:19

标签: c# generics

我正在尝试为每种数据类型简化具有不同堆栈的基于堆栈的解释器。

基本上,我有一堆如下所示的指令:

class Interpreter {
    Stack<int> IntStack;
    Stack<float> FloatStack;
    Stack<char> CharStack;
    // ... Continued for various other types.
}

class PushInt : Instruction {
    Interpreter interpreter;
    int value;

    void Execute() {
        interpreter.IntStack.Push(value);
    }
}

class PopInt : Instruction {
    Interpreter interpreter;

    void Execute() {
        interpreter.IntStack.Pop();
    }
}

对于PushFloatPopFloatPushCharPopChar等,我有相同的看法

这让我大叫“泛型”,我想做的是改为定义Push<T>Pop<T>。但是我在这里遇到了一个问题:

class Push<T> : Instruction {
    Interpreter interpreter;
    T value;

    void Execute() {
        interpreter.[What on earth goes here?].Push(value);
    }
}

class Pop<T> : Instruction {
    Interpreter interpreter;

    void Execute() {
        interpreter.[What on earth goes here?].Pop();
    }
}

我认为,肯定有一种定义Interpreter.Push<T>(value)的方法,但是我在该方法中遇到了同样的问题-我不确定如何根据泛型类型参数选择正确的堆栈不用一团糟。

我已经尝试过的东西:

我尝试过的一种解决方案是使用静态类:

public static class ArgumentStack<T> {
    public static Stack<T> Stack;

    public static void Push(T value) {
        Stack.Push(value);
    }

    public static T Pop() {
        return Stack.Pop();
    }
}

public class Push<T> : Instruction {
    T value;

    public override void Execute() {
        ArgumentStack<T>.Push(Value);
    }
}

public class Pop<T> : Instruction {
    public override void Execute() {
        ArgumentStack<T>.Pop();
    }
}

但是问题是,如果一次使用多个解释器,那么整个事情就会开始崩溃,因为每种堆栈类型只有一个实例存在,并且所有解释器都可以共享它们。

令人沮丧的是,静态方法几乎完全是我想要的用法,我只是希望我可以在每个Interpreter对象中而不是在类级别进行操作。

有什么想法吗?

3 个答案:

答案 0 :(得分:0)

您是对的,泛型是此处的关键。似乎您只是希望一个类(解释器)在其中具有未知类型的Stack属性。从那里,您希望能够使用Push和Pop这样在堆栈中添加和删除项目:

public class Interpreter<T>
{
    public Stack<T> TypeStack { get; set; }

    public Interpreter()
    {
        if (this.TypeStack == null)
        {
            this.TypeStack = new Stack<T>();
        }
    }
    public void Push(T value)
    {
        this.TypeStack.Push(value);
    }

    public void Pop()
    {
        this.TypeStack.Pop();
    }
}

您将像这样使用它:

  var interpreter = new Interpreter<int>();
  interpreter.Push(5);
  interpreter.Pop();

答案 1 :(得分:0)

我想出了一个我很满意的解决方案。但是,如果有人有更漂亮的东西,这个问题就可以解决。

我在静态通用类中定义了一个静态字典,该字典将解释器映射到各个堆栈,然后定义了一个解释器方法,该方法检索正确的通用堆栈。下面的代码示例:


public static class ArgumentStack<T> {
    public static Dictionary<Interpreter, Stack<T>> Stacks;
}


public class Interpreter {
    public Stack<T> GetStack<T>() {
        return ArgumentStack<T>.Stacks[this];
    }
}


public class Push<T> : Instruction {
    Interpreter interpreter;
    T value;

    public override void Execute() {
        interpreter.GetStack<T>().Push(Value);
    }
}

public class Pop<T> : Instruction {
    Interpreter interpreter;

    public override void Execute() {
        interpreter.GetStack<T>().Pop();
    }
}

答案 2 :(得分:0)

这是您可能会适应的方法。看来您希望能够使用许多不同的类型,同时根据它们的类型将它们分别路由到自己的堆栈。 (很难理解您将要使用它的什么地方,但是我会留给您。)

public class Interpreter
{
    private readonly Dictionary<Type, Stack> _stacksByType = new Dictionary<Type, Stack>();

    public void Push<T>(T item) 
    {
        if (!_stacksByType.ContainsKey(typeof(T)))
        {
            _stacksByType.Add(typeof(T), new Stack());
        }
        _stacksByType[typeof(T)].Push(item);
    }

    public T Pop<T>()
    {
        // If we pop an empty stack it throws an InvalidOperationException
        // so I'm throwing the same exception if there's no corresponding stack.
        if (!_stacksByType.ContainsKey(typeof(T)))
            throw new InvalidOperationException("There is nothing in the stack.");
        return (T)_stacksByType[typeof(T)].Pop();
    }
}

这允许Interpreter封装其所有各种堆栈。 Push项时,它会根据其类型将其放入Stack中。如果堆栈不存在,则会创建堆栈。

Pop会根据类型从堆栈中弹出。如果该类型的堆栈不存在,它将抛出与堆栈为空时相同的异常。

因为我们通过控制项目进入哪个Stack来管理类型安全,所以我们不需要通用的Stack<T>

这种封装还可以避免这种情况:

interpreter.IntStack.Push(value);

如果Interpreter之外的类不知道它如何在内部管理所有这些,那就更好了。不必告诉类将值放入哪个堆栈,我们只需给它提供值并让其计算出来即可。