为什么 .NET 4.0 协方差在此示例中不起作用?

发布于 2024-10-31 11:34:55 字数 2649 浏览 2 评论 0 原文

我在以下几行收到编译器错误:

            RenderLookup(Cars);
            RenderLookup(Employees);


Error   2   Argument 1: cannot convert from 'Demo.MyList<Demo.Car>' to 'System.Collections.Generic.IEnumerable<Demo.KeyedBase>' Program.cs  85  26  Demo

Error   4   Argument 1: cannot convert from 'Demo.MyList<Demo.Employee>' to 'System.Collections.Generic.IEnumerable<Demo.KeyedBase>'    Program.cs  86  26  Demo

这是怎么回事?我认为 .NET 4.0 可以处理这个问题?我缺少什么?

using System;
using System.Collections.Generic;

namespace Demo
{
    public interface KeyedBase
    {
        int Key { get; }
        string Description { get; }
    }

    public class MyList<T> : Dictionary<int, T> where T : KeyedBase
    {
        public void Add(T itm)
        {
            Add(itm.Key, itm);
        }
    }

    public class Car : KeyedBase
    {
        private readonly int _ID;
        private readonly string _Description;
        public Car(int ID, string Description)
        {
            _ID = ID;
            _Description = Description;
        }

        public int Key
        {
            get { return _ID; }
        }

        public string Description
        {
            get { return _Description; }
        }
    }

    public class Employee : KeyedBase
    {
        private readonly int _ID;
        private readonly string _FirstName;
        private readonly string _LastName;
        public Employee(int ID, string FirstName, string LastName)
        {
            _ID = ID;
            _FirstName = FirstName;
            _LastName = LastName;
        }

        public int Key
        {
            get { return _ID; }
        }

        public string Description
        {
            get { return _LastName + ", " + _FirstName; }
        }
    }

    class Program
    {
        private static void RenderLookup(IEnumerable<KeyedBase> Lookup)
        {
            Console.WriteLine("Choose:");
            foreach (var itm in Lookup)
            {
                Console.WriteLine("{0} : {1}", itm.Key, itm.Description);
            }
        }

        static void Main(string[] args)
        {
            var Cars = new MyList<Car> { new Car(1, "Subaru"), new Car(2, "Volswagen") };

            var Employees = new MyList<Employee>
                                {
                                    new Employee(1, "Mickey", "Mouse"),
                                    new Employee(2, "Minnie", "Mouse")
                                };

            RenderLookup(Cars);
            RenderLookup(Employees);
        }
    }
}

I'm getting compiler errors on these lines:

            RenderLookup(Cars);
            RenderLookup(Employees);


Error   2   Argument 1: cannot convert from 'Demo.MyList<Demo.Car>' to 'System.Collections.Generic.IEnumerable<Demo.KeyedBase>' Program.cs  85  26  Demo

Error   4   Argument 1: cannot convert from 'Demo.MyList<Demo.Employee>' to 'System.Collections.Generic.IEnumerable<Demo.KeyedBase>'    Program.cs  86  26  Demo

What's the deal? I thought .NET 4.0 would handle this? What am I missing?

using System;
using System.Collections.Generic;

namespace Demo
{
    public interface KeyedBase
    {
        int Key { get; }
        string Description { get; }
    }

    public class MyList<T> : Dictionary<int, T> where T : KeyedBase
    {
        public void Add(T itm)
        {
            Add(itm.Key, itm);
        }
    }

    public class Car : KeyedBase
    {
        private readonly int _ID;
        private readonly string _Description;
        public Car(int ID, string Description)
        {
            _ID = ID;
            _Description = Description;
        }

        public int Key
        {
            get { return _ID; }
        }

        public string Description
        {
            get { return _Description; }
        }
    }

    public class Employee : KeyedBase
    {
        private readonly int _ID;
        private readonly string _FirstName;
        private readonly string _LastName;
        public Employee(int ID, string FirstName, string LastName)
        {
            _ID = ID;
            _FirstName = FirstName;
            _LastName = LastName;
        }

        public int Key
        {
            get { return _ID; }
        }

        public string Description
        {
            get { return _LastName + ", " + _FirstName; }
        }
    }

    class Program
    {
        private static void RenderLookup(IEnumerable<KeyedBase> Lookup)
        {
            Console.WriteLine("Choose:");
            foreach (var itm in Lookup)
            {
                Console.WriteLine("{0} : {1}", itm.Key, itm.Description);
            }
        }

        static void Main(string[] args)
        {
            var Cars = new MyList<Car> { new Car(1, "Subaru"), new Car(2, "Volswagen") };

            var Employees = new MyList<Employee>
                                {
                                    new Employee(1, "Mickey", "Mouse"),
                                    new Employee(2, "Minnie", "Mouse")
                                };

            RenderLookup(Cars);
            RenderLookup(Employees);
        }
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

俯瞰星空 2024-11-07 11:34:55

您正在尝试将 Dictionary 派生类转换为 IEnumerable。您必须将其转换为 IEnumerable> 或从字典传递 Values 集合。

RenderLookup(Cars.Values);
RenderLookup(Employees.Values);

这不是协方差的问题,而只是类型兼容性的问题。

You're trying to cast Dictionary<int, T>-derived class into IEnumerable<T>. You have to cast it to IEnumerable<KeyValuePair<int, T>> or pass Values collection from your dictionaries.

RenderLookup(Cars.Values);
RenderLookup(Employees.Values);

This is not a problem with covariance but just with type compatibility.

难理解 2024-11-07 11:34:55

首先,协/逆变支持是可选的。您需要将限定符 outin 添加到泛型类型参数中才能使其正常工作(在接口上,而不是在类上)。因此,您必须:

interface IMyList<(in/out) T> : ...

其次,您不能在示例中执行此操作,因为您的参数 T 既是协变的,又是逆变的。为了简化,仅由类返回的类型参数可以是协变的(又名 out),仅由类接受的类型参数是逆变的(又名 in) >)。您的类既接受并返回T

最后,您的真正问题是 Dictionary 不是 IEnumerable,而是 IEnumerable K,V>>。您需要像这样重写 MyList 上的 GetEnumerator 方法:

public class MyList<T> : Dictionary<int, T>, IEnumerable<T> where T : KeyedBase
{
    public void Add(T itm)
    {
        Add(itm.Key, itm);
    }

    public virtual IEnumerator<T> GetEnumerator()
    {
        return Values.GetEnumerator();
    }
}

然后您的示例应该可以工作。

First of all, co-/contravariance support is opt-in. You need to add the qualifiers out or in to a generic type parameter in order for it to work (on an interface, not a class). So you'd have to have:

interface IMyList<(in/out) T> : ...

Secondly, you can't do this in your example because your parameter T is both co- and contravariant. To simplify, a type parameter that is only returned by a class can be covariant (aka out), and a type parameter that is only accepted by a class is contravariant (aka in). Your class both accepts and returns T.

Lastly, your real problem is that Dictionary<K,V> isn't IEnumerable<V>, but IEnumerable<KeyValuePair<K,V>>. You'll need to override the GetEnumerator method on MyList like so:

public class MyList<T> : Dictionary<int, T>, IEnumerable<T> where T : KeyedBase
{
    public void Add(T itm)
    {
        Add(itm.Key, itm);
    }

    public virtual IEnumerator<T> GetEnumerator()
    {
        return Values.GetEnumerator();
    }
}

Your example should then work.

筱果果 2024-11-07 11:34:55

MyList 间接实现 IEnumerable> (通过 Dictionary),因此它不是与 IEnumerable 不兼容。

MyList<Car> indirectly implements IEnumerable<KeyValuePair<int, Car>> (via Dictionary<int,Car>) and so it isn't compatible with IEnumerable<KeyedBase>.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文