转换列表列出

发布于 2025-01-20 21:02:16 字数 320 浏览 4 评论 0原文

虽然我们可以从基类/界面继承,但为什么我们不能声明list< 使用相同的类/接口?

interface A
{ }

class B : A
{ }

class C : B
{ }

class Test
{
    static void Main(string[] args)
    {
        A a = new C(); // OK
        List<A> listOfA = new List<C>(); // compiler Error
    }
}

有办法吗?

While we can inherit from base class/interface, why can't we declare a List<>
using same class/interface?

interface A
{ }

class B : A
{ }

class C : B
{ }

class Test
{
    static void Main(string[] args)
    {
        A a = new C(); // OK
        List<A> listOfA = new List<C>(); // compiler Error
    }
}

Is there a way around?

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

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

发布评论

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

评论(15

千紇 2025-01-27 21:02:17

首先,停止使用 A、B、C 等难以理解的类名。使用 Animal、Mammal、Giraffe、Food、Fruit、Orange 等关系明确的名称。

那么你的问题是“为什么我不能将长颈鹿列表分配给动物类型列表的变量,因为我可以将长颈鹿分配给动物类型的变量?”

答案是:假设你可以。那么会出现什么问题呢?

好吧,您可以将老虎添加到动物列表中。假设我们允许您将长颈鹿列表放入保存动物列表的变量中。然后你尝试将一只老虎添加到该列表中。会发生什么?您希望长颈鹿列表中包含老虎吗?你想要崩溃吗?或者您是否希望编译器通过首先将赋值设置为非法来保护您免受崩溃?

我们选择后者。

这种转换称为“协变”转换。在 C# 4 中,当已知转换始终安全时,我们将允许您在接口和委托上进行协变转换。有关详细信息,请参阅我关于协变和逆变的博客文章。 (本周的周一和周四都会有关于这个主题的新内容。)

First of all, stop using impossible-to-understand class names like A, B, C. Use Animal, Mammal, Giraffe, or Food, Fruit, Orange or something where the relationships are clear.

Your question then is "why can I not assign a list of giraffes to a variable of type list of animal, since I can assign a giraffe to a variable of type animal?"

The answer is: suppose you could. What could then go wrong?

Well, you can add a Tiger to a list of animals. Suppose we allow you to put a list of giraffes in a variable that holds a list of animals. Then you try to add a tiger to that list. What happens? Do you want the list of giraffes to contain a tiger? Do you want a crash? or do you want the compiler to protect you from the crash by making the assignment illegal in the first place?

We choose the latter.

This kind of conversion is called a "covariant" conversion. In C# 4 we will allow you to make covariant conversions on interfaces and delegates when the conversion is known to be always safe. See my blog articles on covariance and contravariance for details. (There will be a fresh one on this topic on both Monday and Thursday of this week.)

贵在坚持 2025-01-27 21:02:17

引用Eric的精彩解释

会发生什么?您希望长颈鹿列表中包含老虎吗?你想要崩溃吗?或者您是否希望编译器通过首先将赋值设置为非法来保护您免受崩溃?
我们选择后者。

但是,如果您想选择运行时崩溃而不是编译错误怎么办?您通常会使用 Cast<>或ConvertAll<>但这样你会遇到两个问题:它将创建列表的副本。如果您在新列表中添加或删除某些内容,这不会反映在原始列表中。其次,由于它使用现有对象创建新列表,因此会带来很大的性能和内存损失。

我遇到了同样的问题,因此我创建了一个包装类,它可以投射通用列表,而无需创建全新的列表。

在最初的问题中,您可以使用:

class Test
{
    static void Main(string[] args)
    {
        A a = new C(); // OK
        IList<A> listOfA = new List<C>().CastList<C,A>(); // now ok!
    }
}

这里是包装类(+一个扩展方法 CastList 以便于使用)

public class CastedList<TTo, TFrom> : IList<TTo>
{
    public IList<TFrom> BaseList;

    public CastedList(IList<TFrom> baseList)
    {
        BaseList = baseList;
    }

    // IEnumerable
    IEnumerator IEnumerable.GetEnumerator() { return BaseList.GetEnumerator(); }

    // IEnumerable<>
    public IEnumerator<TTo> GetEnumerator() { return new CastedEnumerator<TTo, TFrom>(BaseList.GetEnumerator()); }

    // ICollection
    public int Count { get { return BaseList.Count; } }
    public bool IsReadOnly { get { return BaseList.IsReadOnly; } }
    public void Add(TTo item) { BaseList.Add((TFrom)(object)item); }
    public void Clear() { BaseList.Clear(); }
    public bool Contains(TTo item) { return BaseList.Contains((TFrom)(object)item); }
    public void CopyTo(TTo[] array, int arrayIndex) { BaseList.CopyTo((TFrom[])(object)array, arrayIndex); }
    public bool Remove(TTo item) { return BaseList.Remove((TFrom)(object)item); }

    // IList
    public TTo this[int index]
    {
        get { return (TTo)(object)BaseList[index]; }
        set { BaseList[index] = (TFrom)(object)value; }
    }

    public int IndexOf(TTo item) { return BaseList.IndexOf((TFrom)(object)item); }
    public void Insert(int index, TTo item) { BaseList.Insert(index, (TFrom)(object)item); }
    public void RemoveAt(int index) { BaseList.RemoveAt(index); }
}

public class CastedEnumerator<TTo, TFrom> : IEnumerator<TTo>
{
    public IEnumerator<TFrom> BaseEnumerator;

    public CastedEnumerator(IEnumerator<TFrom> baseEnumerator)
    {
        BaseEnumerator = baseEnumerator;
    }

    // IDisposable
    public void Dispose() { BaseEnumerator.Dispose(); }

    // IEnumerator
    object IEnumerator.Current { get { return BaseEnumerator.Current; } }
    public bool MoveNext() { return BaseEnumerator.MoveNext(); }
    public void Reset() { BaseEnumerator.Reset(); }

    // IEnumerator<>
    public TTo Current { get { return (TTo)(object)BaseEnumerator.Current; } }
}

public static class ListExtensions
{
    public static IList<TTo> CastList<TFrom, TTo>(this IList<TFrom> list)
    {
        return new CastedList<TTo, TFrom>(list);
    }
}

To quote the great explanation of Eric

What happens? Do you want the list of giraffes to contain a tiger? Do you want a crash? or do you want the compiler to protect you from the crash by making the assignment illegal in the first place?
We choose the latter.

But what if you want to choose for a runtime crash instead of a compile error? You would normally use Cast<> or ConvertAll<> but then you will have 2 problems: It will create a copy of the list. If you add or remove something in the new list, this won't be reflected in the original list. And secondly, there is a big performance and memory penalty since it creates a new list with the existing objects.

I had the same problem and therefore I created a wrapper class that can cast a generic list without creating an entirely new list.

In the original question you could then use:

class Test
{
    static void Main(string[] args)
    {
        A a = new C(); // OK
        IList<A> listOfA = new List<C>().CastList<C,A>(); // now ok!
    }
}

and here the wrapper class (+ an extention method CastList for easy use)

public class CastedList<TTo, TFrom> : IList<TTo>
{
    public IList<TFrom> BaseList;

    public CastedList(IList<TFrom> baseList)
    {
        BaseList = baseList;
    }

    // IEnumerable
    IEnumerator IEnumerable.GetEnumerator() { return BaseList.GetEnumerator(); }

    // IEnumerable<>
    public IEnumerator<TTo> GetEnumerator() { return new CastedEnumerator<TTo, TFrom>(BaseList.GetEnumerator()); }

    // ICollection
    public int Count { get { return BaseList.Count; } }
    public bool IsReadOnly { get { return BaseList.IsReadOnly; } }
    public void Add(TTo item) { BaseList.Add((TFrom)(object)item); }
    public void Clear() { BaseList.Clear(); }
    public bool Contains(TTo item) { return BaseList.Contains((TFrom)(object)item); }
    public void CopyTo(TTo[] array, int arrayIndex) { BaseList.CopyTo((TFrom[])(object)array, arrayIndex); }
    public bool Remove(TTo item) { return BaseList.Remove((TFrom)(object)item); }

    // IList
    public TTo this[int index]
    {
        get { return (TTo)(object)BaseList[index]; }
        set { BaseList[index] = (TFrom)(object)value; }
    }

    public int IndexOf(TTo item) { return BaseList.IndexOf((TFrom)(object)item); }
    public void Insert(int index, TTo item) { BaseList.Insert(index, (TFrom)(object)item); }
    public void RemoveAt(int index) { BaseList.RemoveAt(index); }
}

public class CastedEnumerator<TTo, TFrom> : IEnumerator<TTo>
{
    public IEnumerator<TFrom> BaseEnumerator;

    public CastedEnumerator(IEnumerator<TFrom> baseEnumerator)
    {
        BaseEnumerator = baseEnumerator;
    }

    // IDisposable
    public void Dispose() { BaseEnumerator.Dispose(); }

    // IEnumerator
    object IEnumerator.Current { get { return BaseEnumerator.Current; } }
    public bool MoveNext() { return BaseEnumerator.MoveNext(); }
    public void Reset() { BaseEnumerator.Reset(); }

    // IEnumerator<>
    public TTo Current { get { return (TTo)(object)BaseEnumerator.Current; } }
}

public static class ListExtensions
{
    public static IList<TTo> CastList<TFrom, TTo>(this IList<TFrom> list)
    {
        return new CastedList<TTo, TFrom>(list);
    }
}
清醇 2025-01-27 21:02:17

如果你使用 IEnumerable 代替,它会起作用(至少在 C# 4.0 中,我没有尝试过以前的版本)。这只是一个演员表,当然,它仍然是一个列表。

而不是 -

List listOfA = new List(); // 编译器错误

在问题的原始代码中,使用 -

IEnumerable; listOfA = new List(); // 编译器错误 - 不再有! :)

If you use IEnumerable instead, it will work (at least in C# 4.0, I have not tried previous versions). This is just a cast, of course, it will still be a list.

Instead of -

List<A> listOfA = new List<C>(); // compiler Error

In the original code of the question, use -

IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)

往日 2025-01-27 21:02:17

至于为什么它不起作用,理解协方差和逆变。

只是为了说明为什么这不应该工作,这里对您提供的代码进行了更改:

void DoesThisWork()
{
     List<C> DerivedList = new List<C>();
     List<A> BaseList = DerivedList;
     BaseList.Add(new B());

     C FirstItem = DerivedList.First();
}

这应该工作吗?列表中的第一项是类型“B”,但 DerivedList 项的类型是 C。

现在,假设我们真的只想创建一个通用函数,该函数对实现 A 的某种类型的列表进行操作,但是我们不在乎那是什么类型:

void ThisWorks<T>(List<T> GenericList) where T:A
{

}

void Test()
{
     ThisWorks(new List<B>());
     ThisWorks(new List<C>());
}

As far as why it doesn't work, it might be helpful to understand covariance and contravariance.

Just to show why this shouldn't work, here is a change to the code you provided:

void DoesThisWork()
{
     List<C> DerivedList = new List<C>();
     List<A> BaseList = DerivedList;
     BaseList.Add(new B());

     C FirstItem = DerivedList.First();
}

Should this work? The First item in the list is of Type "B", but the type of the DerivedList item is C.

Now, assume that we really just want to make a generic function that operates on a list of some type which implements A, but we don't care what type that is:

void ThisWorks<T>(List<T> GenericList) where T:A
{

}

void Test()
{
     ThisWorks(new List<B>());
     ThisWorks(new List<C>());
}
抹茶夏天i‖ 2025-01-27 21:02:17

您只能施放阅读列表。例如:

IEnumerable<A> enumOfA = new List<C>();//This works
IReadOnlyCollection<A> ro_colOfA = new List<C>();//This works
IReadOnlyList<A> ro_listOfA = new List<C>();//This works

您不能为支持保存元素的列表做到这一点。原因是:

List<string> listString=new List<string>();
List<object> listObject=(List<object>)listString;//Assume that this is possible
listObject.Add(new object());

现在什么?请记住,ListObject和ListString实际上是同一列表,因此ListString现在具有对象元素 - 不可能,也不是。

You can only cast to readonly lists. For example:

IEnumerable<A> enumOfA = new List<C>();//This works
IReadOnlyCollection<A> ro_colOfA = new List<C>();//This works
IReadOnlyList<A> ro_listOfA = new List<C>();//This works

And you cannot do it for lists that support saving elements. The reason why is:

List<string> listString=new List<string>();
List<object> listObject=(List<object>)listString;//Assume that this is possible
listObject.Add(new object());

What now? Remember that listObject and listString are the same list actually, so listString now have object element - it shouldn't be possible and it's not.

楠木可依 2025-01-27 21:02:17

对于您的问题,有几个本机 c#可能性:

  1. Ireadonlylistienumerable =&gt; 无错误和typesafe。 您可能需要的东西。
  2. 使用list&lt;&gt;正确的方法=&gt; Free / not Typeafe
  3. array < / code> =&gt; typesafe/throws运行时错误
  4. dynamiclist&lt; object&gt; =&gt; gt; 通用/note typeafe/thr thr runtime错误

所有这些都可以很好地工作! 无需任何棘手的编程!

所有示例的接口和类定义:

using System; using System.Collections.Generic; using System.Linq;

interface IAnimal
{
    public string Name { get; }
}
class Bear : IAnimal
{
    public string BearName = "aBear";
    public string Name => BearName;
}
class Cat : IAnimal
{
    public string CatName = "aCat";
    public string Name => CatName;
}
// Dog has no base class/interface; it isn't related to the other classes
// But it also provides a <Name> property (what a coincidence!)
class Dog
{
    public string DogName = "aDog";
    public string Name => DogName;
}

public class AssignDerivedClass {
    private static string AnyName(dynamic anymal) => anymal switch
    {
        IAnimal animal => animal.Name,
        Dog dog => dog.DogName,
        string s => s,
        _ => "Any unknown Animal"
    };

这是每个解决方案的示例:

1。 IREADONLYLISTiEnumerable:您可能需要

  • 列表&lt; c&gt;分配给iEnumerable代码>或ireadonlylist&lt; a&gt;
  • 在运行时都无法更改它们,即您不能添加remove> remove elements。
  • 您仍然可以自己修改数据元素。
  • 使用此iEnumerableIREADONLYLIST非常便宜。只是通过另一个指针访问数据。
  • 但是请注意:如果您附加元素元素到IEnumerable或IreadonlyList,则可以创建一个新的iEnumerable。使用此iEnumerable不当会变得昂贵。

由于无法添加任何元素,因此所有元素仍然是正确的类型:

public static void TestIEnumerableAndIReadonlyList()
{
    var cats = new List<Cat>()
    {
        new Cat() { CatName = "Cat-3" },
    };
    IEnumerable<IAnimal> animalsEnumerable = cats;
    IReadOnlyList<IAnimal> animalsReadOnlyList = cats;

    var extendedEnumerable = animalsReadOnlyList.Append(new Bear());
    (extendedEnumerable.First() as Cat).CatName = "Cat-3a";

    Console.WriteLine("Cat names: {0}, {1}, {2}, {3}",
                      cats.ElementAt(0).CatName,
                      animalsReadOnlyList[^1].Name,
                      AnyName(animalsEnumerable.Last()),
                      AnyName(extendedEnumerable.ElementAt(1)));
}
// Result => Cat names: Cat-3a, Cat-3a, Cat-3a, aBear

2。使用list&lt;&gt;正确的方式: list&lt; a&gt; listofa = new()

  • 定义接口的列表(不是派生类)
  • 仅分配一个派生类的实例 - 无论如何您不想存储其他类,是吗?

将猫添加到应该是熊的动物列表中:

public static void TestListOfInterface()
{
    var bears = new List<IAnimal>()
    {
        new Bear() { BearName = "Bear-1" },
    };
    bears.Add(new Cat() { CatName = "Cat-2" });

    string bearNames = string.Join(", ", bears.Select(animal => animal.Name));
    Console.WriteLine($"Bear names: {bearNames}");

    static string VerifyBear(IAnimal bear)
        => (bear as Bear)?.Name ?? "disguised as a bear!!!";

    string bearInfo0 = VerifyBear(bears[0]);
    string bearInfo1 = VerifyBear(bears[1]);
    Console.WriteLine($"One animal is {bearInfo0}, the other one is {bearInfo1}");
}
// Bear names: Bear-1, Cat-2
// One animal is Bear-1, the other one is disguised as a bear!!!

3。 数组:创建bear []数组,可以保证所有数组元素参考实例bear

  • 您可以交换元素,但是您无法删除或添加新元素。
  • 尝试设置错误的类型会产生运行时错误。

在数组中携带:

public static void TestArray()
{
    Bear[] bears = { new Bear(), null };
    IAnimal[] bearAnimals = bears;

    try { bearAnimals[1] = new Cat(); } // => ArrayTypeMismatchException
    catch (Exception e) { Console.Error.WriteLine(e); } // type incompatible with array

    bearAnimals[1] = new Bear() { BearName = "Bear-2" };
    Console.WriteLine($"Bear names: {bearAnimals[0].Name}, {bears[1].BearName}");
}
// Result => Bear names: aBear, Bear-2

4。 动态list&lt; dynamic&gt;:最通用的解决方案

  • 类型在运行时检查
  • 您放弃编译器错误检查支持支持,因此请谨慎处理!
  • 如果您尝试添加错误类型的元素,则只会遇到运行时错误!
  • 如果您访问不存在的成员,则只会遇到运行时错误!
  • 您甚至可以分配无关类的集合。

将您的列表分配给dynamic或使用list&lt; dynamic&gt;

public static void TestDynamicListAndArray()
{
    dynamic any = new List<Cat>()   // List of specific class - or of interface
    {
        new Cat() { CatName = "Cat-1" },
        new Cat() { CatName = "Cat-2" },
    };
    try { any[0].BearName = "Bear-1"; } // => RuntimeBinderException
    catch (Exception e) { Console.Error.WriteLine(e); } // Cat has no BearName
    try { any.Add(new Bear()); } // => RuntimeBinderException
    catch (Exception e) { Console.Error.WriteLine(e); } // no matching overload

    any[1].CatName += 'a';
    Console.WriteLine($"Animal names: {any[0].CatName}, {any[1].Name}");

    var mix = new List<dynamic>
    {
        new Bear() {BearName = "Bear-3"},
        new Dog() {DogName = "Dog-4"},
        "Cat-5",  // 100MHz ;-)
    };
    Console.WriteLine($"{AnyName(mix[0])}, {mix[1].Name}, {AnyName(mix[2])}");

    try { Console.WriteLine($"Names: {any[2].Name}"); } // => RuntimeBinderException
    catch (Exception e) { Console.Error.WriteLine(e); } // no definition for 'Name'

    any = new Bear() { BearName = "Bear-6" }; // Scalar - not a List or Array!
    try { Console.WriteLine($"{AnyName(any[0])}"); } // => RuntimeBinderException
    catch (Exception e) { Console.Error.WriteLine(e); } // cannot apply indexing []
}
//Animal names: Bear-1, Cat-2a
//Bear-3, Dog-4, Cat-5
} //end of class AssignDerivedClass

For your problem there are several native C# possibilities:

  1. IReadOnlyList, IEnumerable => error free and typesafe. What you probably need.
  2. Use List<> the proper way => error free / not typesafe
  3. Array => typesafe / throws runtime errors
  4. dynamic or List<object> => universal / not typesafe / throws runtime errors

All of them work well! There is no need for any tricky programming!

Interface and class definitions for all of the examples:

using System; using System.Collections.Generic; using System.Linq;

interface IAnimal
{
    public string Name { get; }
}
class Bear : IAnimal
{
    public string BearName = "aBear";
    public string Name => BearName;
}
class Cat : IAnimal
{
    public string CatName = "aCat";
    public string Name => CatName;
}
// Dog has no base class/interface; it isn't related to the other classes
// But it also provides a <Name> property (what a coincidence!)
class Dog
{
    public string DogName = "aDog";
    public string Name => DogName;
}

public class AssignDerivedClass {
    private static string AnyName(dynamic anymal) => anymal switch
    {
        IAnimal animal => animal.Name,
        Dog dog => dog.DogName,
        string s => s,
        _ => "Any unknown Animal"
    };

Here are examples for each of the solutions:

1. IReadOnlyList, IEnumerable: What you probably need

  • Assign your List<C> to an IEnumerable<A> or IReadOnlyList<A>
  • Neither of them can be changed at runtime, i.e. you can't Add or Remove elements.
  • You can still modify your data elements themselves.
  • Using this IEnumerable or IReadOnlyList is very cheap. Just accessing your data through another pointer.
  • But take care: If you Append elements to IEnumerable or IReadOnlyList, then you create a new IEnumerable. Using this IEnumerable improperly may become expensive.

Since no elements can be added, all elements remain of correct type:

public static void TestIEnumerableAndIReadonlyList()
{
    var cats = new List<Cat>()
    {
        new Cat() { CatName = "Cat-3" },
    };
    IEnumerable<IAnimal> animalsEnumerable = cats;
    IReadOnlyList<IAnimal> animalsReadOnlyList = cats;

    var extendedEnumerable = animalsReadOnlyList.Append(new Bear());
    (extendedEnumerable.First() as Cat).CatName = "Cat-3a";

    Console.WriteLine("Cat names: {0}, {1}, {2}, {3}",
                      cats.ElementAt(0).CatName,
                      animalsReadOnlyList[^1].Name,
                      AnyName(animalsEnumerable.Last()),
                      AnyName(extendedEnumerable.ElementAt(1)));
}
// Result => Cat names: Cat-3a, Cat-3a, Cat-3a, aBear

2. Use List<> the proper way: List<A> listOfA = new()

  • Define a List of your interface (not of a derived class)
  • Assign instances of one derived class only - you didn't want to store other classes anyway, did you?

Adding a Cat to a List of Animals which are supposed to be Bears:

public static void TestListOfInterface()
{
    var bears = new List<IAnimal>()
    {
        new Bear() { BearName = "Bear-1" },
    };
    bears.Add(new Cat() { CatName = "Cat-2" });

    string bearNames = string.Join(", ", bears.Select(animal => animal.Name));
    Console.WriteLine(
quot;Bear names: {bearNames}");

    static string VerifyBear(IAnimal bear)
        => (bear as Bear)?.Name ?? "disguised as a bear!!!";

    string bearInfo0 = VerifyBear(bears[0]);
    string bearInfo1 = VerifyBear(bears[1]);
    Console.WriteLine(
quot;One animal is {bearInfo0}, the other one is {bearInfo1}");
}
// Bear names: Bear-1, Cat-2
// One animal is Bear-1, the other one is disguised as a bear!!!

3. Array: Creating a Bear[] array, it is guaranteed that all array elements reference instances of Bear.

  • You can exchange elements, but you can't remove or add new elements.
  • Trying to set a wrong type yields a runtime error.

Bears in an array:

public static void TestArray()
{
    Bear[] bears = { new Bear(), null };
    IAnimal[] bearAnimals = bears;

    try { bearAnimals[1] = new Cat(); } // => ArrayTypeMismatchException
    catch (Exception e) { Console.Error.WriteLine(e); } // type incompatible with array

    bearAnimals[1] = new Bear() { BearName = "Bear-2" };
    Console.WriteLine(
quot;Bear names: {bearAnimals[0].Name}, {bears[1].BearName}");
}
// Result => Bear names: aBear, Bear-2

4. dynamic and List<dynamic>: The most universal solution

  • Type checking at runtime
  • You abandon your compiler error checking support, so handle with care!
  • If you try to add an element of wrong type, you'll only get a runtime error!
  • If you access a non-existing member, you'll only get a runtime error!
  • You can even assign collections of unrelated classes.

Assign your list to dynamic or use List<dynamic>:

public static void TestDynamicListAndArray()
{
    dynamic any = new List<Cat>()   // List of specific class - or of interface
    {
        new Cat() { CatName = "Cat-1" },
        new Cat() { CatName = "Cat-2" },
    };
    try { any[0].BearName = "Bear-1"; } // => RuntimeBinderException
    catch (Exception e) { Console.Error.WriteLine(e); } // Cat has no BearName
    try { any.Add(new Bear()); } // => RuntimeBinderException
    catch (Exception e) { Console.Error.WriteLine(e); } // no matching overload

    any[1].CatName += 'a';
    Console.WriteLine(
quot;Animal names: {any[0].CatName}, {any[1].Name}");

    var mix = new List<dynamic>
    {
        new Bear() {BearName = "Bear-3"},
        new Dog() {DogName = "Dog-4"},
        "Cat-5",  // 100MHz ;-)
    };
    Console.WriteLine(
quot;{AnyName(mix[0])}, {mix[1].Name}, {AnyName(mix[2])}");

    try { Console.WriteLine(
quot;Names: {any[2].Name}"); } // => RuntimeBinderException
    catch (Exception e) { Console.Error.WriteLine(e); } // no definition for 'Name'

    any = new Bear() { BearName = "Bear-6" }; // Scalar - not a List or Array!
    try { Console.WriteLine(
quot;{AnyName(any[0])}"); } // => RuntimeBinderException
    catch (Exception e) { Console.Error.WriteLine(e); } // cannot apply indexing []
}
//Animal names: Bear-1, Cat-2a
//Bear-3, Dog-4, Cat-5
} //end of class AssignDerivedClass
与他有关 2025-01-27 21:02:17

您还可以使用 System.Runtime.CompilerServices.Unsafe NuGet 包创建对同一 List 的引用:

using System.Runtime.CompilerServices;
...
class Tool { }
class Hammer : Tool { }
...
var hammers = new List<Hammer>();
...
var tools = Unsafe.As<List<Tool>>(hammers);

根据上面的示例,您可以访问现有的 使用 tools 变量锤击列表中的 实例。将 Tool 实例添加到列表中会引发 ArrayTypeMismatchException 异常,因为 tools 引用与 hammers 相同的变量。

You can also use the System.Runtime.CompilerServices.Unsafe NuGet package to create a reference to the same List:

using System.Runtime.CompilerServices;
...
class Tool { }
class Hammer : Tool { }
...
var hammers = new List<Hammer>();
...
var tools = Unsafe.As<List<Tool>>(hammers);

Given the sample above, you can access the existing Hammer instances in the list using the tools variable. Adding Tool instances to the list throws an ArrayTypeMismatchException exception because tools references the same variable as hammers.

叹梦 2025-01-27 21:02:17

我个人喜欢创建具有扩展课程的Libs

public static List<TTo> Cast<TFrom, TTo>(List<TFrom> fromlist)
  where TFrom : class 
  where TTo : class
{
  return fromlist.ConvertAll(x => x as TTo);
}

I personally like to create libs with extensions to the classes

public static List<TTo> Cast<TFrom, TTo>(List<TFrom> fromlist)
  where TFrom : class 
  where TTo : class
{
  return fromlist.ConvertAll(x => x as TTo);
}
不爱素颜 2025-01-27 21:02:17

因为 C# 不允许这种类型的继承转换目前

Because C# doesn't allow that type of inheritance conversion at the moment.

因为看清所以看轻 2025-01-27 21:02:17

这是Bigjim的Brilliant 答案的扩展。

就我而言,我有一个带有儿童字典的NodeBase类,我需要一种方法来从孩子那里进行O(1)查找。我试图在儿童的Getter中返回一个私人字典字段,因此显然我想避免昂贵的复制/迭代。因此,我使用BigJim的代码将字典&lt;到通用dictionary&lt; nodebase&gt;的任何特定类型>

// Abstract parent class
public abstract class NodeBase
{
    public abstract IDictionary<string, NodeBase> Children { get; }
    ...
}

// Implementing child class
public class RealNode : NodeBase
{
    private Dictionary<string, RealNode> containedNodes;

    public override IDictionary<string, NodeBase> Children
    {
        // Using a modification of Bigjim's code to cast the Dictionary:
        return new IDictionary<string, NodeBase>().CastDictionary<string, RealNode, NodeBase>();
    }
    ...
}

这很好。但是,我最终遇到了无关的局限性,最终在基类中创建了一个摘要findchild()方法,可以进行查找。事实证明,这首先消除了对演员词典的需求。 (出于我的目的,我能够用简单的iEnumerable替换。)

因此,您可能会问的问题(尤其是如果绩效是禁止您使用.cast&lt;&gt;&gt; 或.convertall&lt;&gt;)是:

“我真的需要施放整个集合,还是可以使用抽象方法来保留执行任务所需的特殊知识,从而避免直接访问该系列?”

有时最简单的解决方案是最好的。

This is an extension to BigJim's brilliant answer.

In my case I had a NodeBase class with a Children dictionary, and I needed a way to generically do O(1) lookups from the children. I was attempting to return a private dictionary field in the getter of Children, so obviously I wanted to avoid expensive copying/iterating. Therefore I used Bigjim's code to cast the Dictionary<whatever specific type> to a generic Dictionary<NodeBase>:

// Abstract parent class
public abstract class NodeBase
{
    public abstract IDictionary<string, NodeBase> Children { get; }
    ...
}

// Implementing child class
public class RealNode : NodeBase
{
    private Dictionary<string, RealNode> containedNodes;

    public override IDictionary<string, NodeBase> Children
    {
        // Using a modification of Bigjim's code to cast the Dictionary:
        return new IDictionary<string, NodeBase>().CastDictionary<string, RealNode, NodeBase>();
    }
    ...
}

This worked well. However, I eventually ran into unrelated limitations and ended up creating an abstract FindChild() method in the base class that would do the lookups instead. As it turned out this eliminated the need for the casted dictionary in the first place. (I was able to replace it with a simple IEnumerable for my purposes.)

So the question you might ask (especially if performance is an issue prohibiting you from using .Cast<> or .ConvertAll<>) is:

"Do I really need to cast the entire collection, or can I use an abstract method to hold the special knowledge needed to perform the task and thereby avoid directly accessing the collection?"

Sometimes the simplest solution is the best.

游魂 2025-01-27 21:02:17

我已经阅读了整篇文章,我只想指出我认为不一致的地方。

编译器阻止您使用列表进行赋值:

List<Tiger> myTigersList = new List<Tiger>() { new Tiger(), new Tiger(), new Tiger() };
List<Animal> myAnimalsList = myTigersList;    // Compiler error

但是编译器对于数组来说完全没问题:

Tiger[] myTigersArray = new Tiger[3] { new Tiger(), new Tiger(), new Tiger() };
Animal[] myAnimalsArray = myTigersArray;    // No problem

关于赋值是否已知是安全的的争论在这里分崩离析。我对数组所做的分配不安全。为了证明这一点,如果我继续这样做:

myAnimalsArray[1] = new Giraffe();

我会得到一个运行时异常“ArrayTypeMismatchException”。人们如何解释这一点?如果编译器真的想阻止我做一些愚蠢的事情,它应该阻止我进行数组赋值。

I've read this whole thread, and I just want to point out what seems like an inconsistency to me.

The compiler prevents you from doing the assignment with Lists:

List<Tiger> myTigersList = new List<Tiger>() { new Tiger(), new Tiger(), new Tiger() };
List<Animal> myAnimalsList = myTigersList;    // Compiler error

But the compiler is perfectly fine with arrays:

Tiger[] myTigersArray = new Tiger[3] { new Tiger(), new Tiger(), new Tiger() };
Animal[] myAnimalsArray = myTigersArray;    // No problem

The argument about whether the assignment is known to be safe falls apart here. The assignment I did with the array is not safe. To prove that, if I follow that up with this:

myAnimalsArray[1] = new Giraffe();

I get a runtime exception "ArrayTypeMismatchException". How does one explain this? If the compiler really wants to prevent me from doing something stupid, it should have prevented me from doing the array assignment.

眼眸印温柔 2025-01-27 21:02:17

可能迟到了。

转换为数组也可以完成工作。

main()
{
   List<Camel> camels = new List<Camel>();
   Reproducton(camels.ToArray());
}


public void Reproducton(Animal[] animals)
{
    foreach(var animal in animals.ToList())
    {
       var baby = animal.Reproduce();
    }
}

May be late.

Conversion to Array can also do the job.

main()
{
   List<Camel> camels = new List<Camel>();
   Reproducton(camels.ToArray());
}


public void Reproducton(Animal[] animals)
{
    foreach(var animal in animals.ToList())
    {
       var baby = animal.Reproduce();
    }
}
神经暖 2025-01-27 21:02:17

我发现的最快方法是复制内部数组。

public static List<T2> CastToList<T, T2>(this List<T> list1)
{
    if (list1 is List<T2> t2)
        return t2;

    var len = list1.Count;

    var srcArray = list1.GetItemsInternal(); // get list._items field, cached delegate

    var ret = new List<T2>(len);
    var dstArray = ret.GetItemsInternal(); // get list._items field

    // fast copy, bulk operation
    Array.Copy(srcArray, 0, dstArray, 0, len);
    CollectionsMarshal.SetCount(ret, len);

    return ret;
}

这是我的测试中的30倍,比list.cast&lt; x&gt;()。tolist(),尤其是有很多元素。
如果您使用不安全的代码等,可能会更快的解决方案。

The fastest way i found is to copy the internal array.

public static List<T2> CastToList<T, T2>(this List<T> list1)
{
    if (list1 is List<T2> t2)
        return t2;

    var len = list1.Count;

    var srcArray = list1.GetItemsInternal(); // get list._items field, cached delegate

    var ret = new List<T2>(len);
    var dstArray = ret.GetItemsInternal(); // get list._items field

    // fast copy, bulk operation
    Array.Copy(srcArray, 0, dstArray, 0, len);
    CollectionsMarshal.SetCount(ret, len);

    return ret;
}

This is in my tests 30x faster than list.Cast<X>().ToList(), especially with lots of elements.
There are probably faster solutions if you use unsafe code etc.

命比纸薄 2025-01-27 21:02:17

认为我会把它扔到这里,因为如果这会使我绊倒,通常是当我传递ilist&lt; giraffe&gt;到采用iList&lt; andial&gt; 。

如果您可以更改该方法,请使用约束的使其通用。这将强制执行类型的安全性,以便您在方法调用期间不能将任何犀牛添加到列表中,稍后您可以再次使用Rhinos列表再次调用该方法。

public void ProcessAnimals( IList<Animal> animals )

public void ProcessAnimals<T>( IList<T> animals ) where T : Animal

Figure I'd throw this here, because if this trips me up, it's usually when I'm passing my IList<Giraffe> to a method that takes a IList<Animal>.

If you can change the method, make it generic with a where constraint. This will enforce type safety such that you can't add any rhinos to your list during the method call, and later you can call the method again with a whole list of rhinos.

public void ProcessAnimals( IList<Animal> animals )

to

public void ProcessAnimals<T>( IList<T> animals ) where T : Animal
幽梦紫曦~ 2025-01-27 21:02:16

完成这项工作的方法是迭代列表并转换元素。这可以使用 ConvertAll 来完成:

List<A> listOfA = new List<C>().ConvertAll(x => (A)x);

您也可以使用 Linq:

List<A> listOfA = new List<C>().Cast<A>().ToList();

The way to make this work is to iterate over the list and cast the elements. This can be done using ConvertAll:

List<A> listOfA = new List<C>().ConvertAll(x => (A)x);

You could also use Linq:

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