如何在参数类型为List<BaseClass>时传递List<DerivedClass>?

16

如何在方法期望一个基本对象列表的情况下传递一个派生对象列表?我正在转换.ToList<BaseClass>() 列表,并想知道是否有更好的方法。 我的第二个问题是语法不正确。 我正在尝试通过引用传递列表,但是我收到了一个错误:'ref'参数未被归类为变量

有什么方法可以解决这两个问题吗?谢谢。

public class BaseClass { }
public class DerivedClass : BaseClass { }

class Program
{
    static void Main(string[] args)
    {
        List<DerivedClass> myDerivedList = new List<DerivedClass>();
        PassList(ref myDerivedList.ToList<BaseClass>());
// SYNTAX ERROR ABOVE IS - 'ref' argument is not classified as a variable

        Console.WriteLine(myDerivedList.Count);
    }

    public static void PassList(ref List<BaseClass> myList)
    {
        myList.Add(new DerivedClass());
        Console.WriteLine(myList.Count);
    }
}

已解决:

类似于这种方法解决了我的问题。

public static void PassList<T>(ref List<T> myList) where T : BaseClass
{
    if (myList == null) myList = new List<T>(); 
    // sorry, i know i left this out of the above example.

    var x = Activator.CreateInstance(typeof(T), new object[] {}) as T;
    myList.Add(x);
    Console.WriteLine(myList.Count);
}

感谢所有帮助解决这个问题以及其他SO问题的人。

2
在这种情况下,为什么你需要用到 ref - zerkms
ref 是必需的,否则添加的项将保留在方法中。 (希望我理解这些术语是正确的)。 - Valamas
1
List 是一个引用类型的类。在方法中添加的项目将被添加到同一实例中。 - zerkms
@Valamas:不,你不理解ref是如何工作的(或者可能不理解引用类型是如何工作的)。请跟随我的回答中的链接并仔细阅读它。 - Jon Skeet
我以前没有见过“where T : BaseClass”的语法,这很有用。感谢您发布已解决的代码。 - Karl Wenzel
3个回答

21

ref部分很简单:要通过引用传递参数,它必须是一个变量。因此,你可以这样写:

List<BaseClass> tmp = myDerivedList.ToList<BaseClass>();
PassList(ref tmp);

...但这不会影响到myDerivedList的值或其内容本身。

这里的ref是无意义的,因为你在方法中从未改变过myList的值。重要的是要理解改变参数值和改变参数值所引用的对象内容之间的区别。有关更多详细信息,请参阅我的参数传递文章

现在为什么不能传递列表呢?这是为了保持类型安全。假设你可以这样做,我们可以写出:

List<OtherDerivedClass> list = new List<OtherDerivedClass>();
PassList(list);

现在我们试图将DerivedClass的一个实例添加到List<OtherDerivedClass>中——这就像将苹果添加到一堆香蕉中一样……它是行不通的!C#编译器会阻止您执行这种不安全的操作,它不会让您将一堆香蕉视为水果碗。假设我们使用Fruit代替BaseClass,并且Banana/Apple作为两个派生类,PassList向其赋值的列表中添加一个Apple:
// This is fine - you can add an apple to a fruit bowl with no problems
List<Fruit> fruitBowl = new List<Fruit>();
PassList(fruitBowl);

// This wouldn't compile because the compiler doesn't "know" that in PassList
// you're only actually adding an apple.
List<Apple> bagOfApples = new List<Apple>();
PassList(bagOfApples);    

// This is the dangerous situation, where you'd be trying to really violate
// type safety, inserting a non-Banana into a bunch of bananas. But the compiler
// can't tell the difference between this and the previous one, based only on
// the fact that you're trying to convert a List<Banana or Apple> to List<Fruit>
List<Banana> bunchOfBananas = new List<Banana>();
PassList(bunchOfBananas );    

C# 4允许在某些情况下使用泛型变异,但在这种特定情况下是无济于事的,因为您正在进行根本不安全的操作。尽管泛型变异是一个相当复杂的主题,但由于您仍在学习参数传递的工作原理,我强烈建议您暂时不要涉及它,直到您对语言的其余部分更加自信。


感谢Jon的解释和链接到您的网站。我已经使用泛型解决了我的问题。 - Valamas
我能问一下 List<BaseClass> tmp = myDerivedList.ToList<BaseClass>(); 到底是什么意思吗?它是创建一个副本的意思吗?我也有类似的需求,但出乎意料的是,如果我这样做并将结果传递给我的方法,所作的更改会反映在原始列表中。(我正在修改对象,而不是从列表中添加或删除任何内容。) - Jonathan Wood
它会创建一个列表的浅拷贝。因此,在原始列表进行添加和删除方面的更改不会在副本中看到,但是对于对象的更改将会看到——因为新列表包含对相同对象的引用。 - Jon Skeet

5
如果您必须保留“ref”,可以这样做:
var list = myDerivedList.ToList<BaseClass>();
PassList(ref list);
// SYNTAX ERROR ABOVE IS - 'ref' argument is not classified as a variable

对于一个ref参数,它需要一个变量来进行引用。如果在程序运行时没有可以被引用的变量,那么这样创建实际上毫无意义。

0

首先,在您提供的代码中,您不需要 ref,因为您实际上并没有以任何方式更改 目标(引用)myList。

然后当然,您会遇到 ref myDerivedList.ToList<..>() 的错误,因为通过引用,您可以更改变量指向的位置,但您没有给出一个变量,而是给出了一个 (所以只需阅读编译器的投诉 ;))

至于您的问题:请阅读有关协变和逆变的信息 - 如果您使用的是 .net 4.0


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接