逆变接口指的是它们自己的类型

时间:2017-06-09 03:48:53

标签: c# generics interface

我有以下路径查找代码:

public interface INode
{
    List<INode> GetNeighbours();
    float GetMovementCost(INode other);
}

public static class Pathfinder
{
    public static List<INode> FindPath(INode origin, INode target) 
    {
        var path = new List<INode>();
        var cameFrom = new Dictionary<INode, INode>();
        var costToNode = new Dictionary<INode, float>();
        var frontier = new PriorityQueue<INode>();
        Dictionary<INode, float> costSoFar = new Dictionary<INode, float>();

        frontier.Enqueue(origin, 0);
        costSoFar[frontier[0].Element] = 0;


        while (frontier.Count > 0)
        {
            INode current = frontier.Dequeue();
            foreach (var neighbour in current.GetNeighbours())
            {
                float newCost = costSoFar[current] +
                                current.GetMovementCost(neighbour);
                if (!costSoFar.ContainsKey(neighbour) || newCost < costSoFar[neighbour])
                {
                    costSoFar[neighbour] = newCost;
                    frontier.Enqueue(neighbour, newCost);
                    cameFrom[neighbour] = current;
                }
            }
            if (current == target)
            {
                break;
            }
        }
        //build path
        INode nn = target;
        while (nn != origin)
        {
            path.Add(nn);
            nn = cameFrom[nn];
        }
        path.Reverse();
        return path;

    }

    public struct PriorityElement<T>
    {
        public T Element;
        public float Priority;

        public PriorityElement(T element, float priority)
        {
            if (priority < 0) throw new Exception("Priorities must be non-negative");
            Element = element;
            Priority = priority;
        }
    }

    public class PriorityQueue<T> : List<PriorityElement<T>>
    {
        public void Enqueue(T element, float priority)
        {
            if (this.Count == 0)
            {
                this.Insert(0, new PriorityElement<T>(element, priority));
                return;
            }
            if (priority > this.Last().Priority)
            {
                this.Add(new PriorityElement<T>(element, priority));
                return;
            }
            var firstLowerThan = this.First(p => priority <= p.Priority);
            this.Insert(IndexOf(firstLowerThan), new PriorityElement<T>(element, priority));
        }

        public T Dequeue()
        {
            if (this.Count == 0)
            {
                throw new Exception("Cannot dequeue, queue is empty");
            }
            var ret = this.First();
            this.Remove(ret);
            return ret.Element;
        }
    }       
}

这样可以正常工作,但真正讨厌的是它返回一个INode,而不是那些暗示INode的东西,所以我必须把它重新投入并且它不是非常类型安全的。

我正在尝试实现此代码的通用版本,类似于:

public static List<Node> FindPath<Node>(Node origin, Node target) where Node : INode<Node>

然而,这显然不起作用,因为List<INode> GetNeighbours();在不使用协方差和逆变的情况下无法返回更多派生类型。在这里实施逆变是棘手的。

有人可以帮我重新定义我的INode界面,以便上述签名有效吗?主要问题是GetNeighbours()需要返回一个移动派生类型的INode列表。

有些事情:

public interface INode
{
    List<T> GetNeighbours<T>() where T : INode; 
    float GetMovementCost(INode other);
}

1 个答案:

答案 0 :(得分:2)

您的FindPath应该使用T类型T : class, INode的参数,而不是明确地使用INode个参数。然后,在方法内部,您应该引用T而不是INode。例如,您可以写:

public static List<T> FindPath<T>(T origin, T target) where T : class, INode
{
    var path = new List<T>();
    var cameFrom = new Dictionary<T, T>();
    var costToNode = new Dictionary<T, float>();
    var frontier = new PriorityQueue<T>();
    Dictionary<T, float> costSoFar = new Dictionary<T, float>();

    frontier.Enqueue(origin, 0);
    costSoFar[frontier[0].Element] = 0;


    while (frontier.Count > 0)
    {
        T current = frontier.Dequeue();
        foreach (var neighbour in current.GetNeighbours<T>())
        {
            float newCost = costSoFar[current] +
                            current.GetMovementCost(neighbour);
            if (!costSoFar.ContainsKey(neighbour) || newCost < costSoFar[neighbour])
            {
                costSoFar[neighbour] = newCost;
                frontier.Enqueue(neighbour, newCost);
                cameFrom[neighbour] = current;
            }
        }
        if (current == target)
        {
            break;
        }
    }
    //build path
    T nn = target;
    while (nn != origin)
    {
        path.Add(nn);
        nn = cameFrom[nn];
    }
    path.Reverse();
    return path;
}

然后只需将您的界面更改为:

public interface INode
{
    List<T> GetNeighbours<T>() where T : INode;
    float GetMovementCost(INode other);
}