我看了一些使用C#泛型的示例代码。什么时候应该使用它们?
所有这些示例都很复杂。我需要一个简单明了的示例,让我开始使用C#泛型。
List<T>
类。它可以容纳任何类型的对象。例如,你可以声明一个字符串列表(new List<string>()
)或者动物列表(new List<Animal>()
),因为它是通用的。ArrayList
类,但不足之处在于它所包含的类型是对象。因此,当你遍历列表时,你必须将每个项目转换为其正确的类型(即 string
或 Animal
),这需要更多的代码并且有性能损失。此外,由于 ArrayList
包含对象,它不是类型安全的。你仍然可以将一个 Animal
添加到一个字符串的 ArrayList
中:ArrayList arrayList = new ArrayList();
arrayList.Add(new Animal());
arrayList.Add("");
因此,当迭代ArrayList时,您必须检查类型以确保实例是特定类型的,这会导致代码质量不佳:
foreach (object o in arrayList)
{
if(o is Animal)
((Animal)o).Speak();
}
使用通用的List<string>
,这是不可能实现的:
List<string> stringList = new List<String>();
stringList.Add("Hello");
stringList.Add("Second String");
stringList.Add(new Animal()); // error! Animal cannot be cast to a string
总结其他答案的一些重点:
1)泛型使您能够编写“通用”代码(即,它将适用于多个类型)。如果您想要编写“通用”行为,并且需要使其对不同的数据类型起作用,则只需编写该代码一次。List的示例是一个很好的例子,您可以需要使用每种类型实例化的相同代码来创建客户、产品、订单、供应商等列表。
// snippet
List<Customer> customers = new List<Customer>();
Customer thisCustomer = new Customer();
customers.Add(thisCustomer);
List<Order> orders = new List<Order>();
Order thatOrder = new Order();
orders.Add(thatOrder);
// etc.
2) 令人惊讶的是,泛型仍然能够保证类型安全!因此,如果您尝试这样做,您将正确地收到一个错误:
// continued for snippet above
Order anotherOrder = new Order();
customers.Add(anotherOrder); // FAIL!
你希望这是一个错误,这样以后你的客户处理代码就不必处理出现在客户列表中的虚假订单。
重复是万恶之源。当你需要在不同类型的数据上执行相同的操作时,就会出现代码重复的情况。泛型允许你避免这种情况,它允许你编写针对“通用”类型的代码,并随后将其替换为特定的类型。
解决此问题的另一种方法是使用类型为“System.Object”的变量,可以向其中分配任何类型的对象。这种方法涉及值类型和引用类型之间的装箱和拆箱操作,会影响性能。而且类型转换会使代码变得不干净。
泛型在MSIL和CLR中得到支持,因此表现非常好。
你应该阅读关于泛型的这些文章 -
http://msdn.microsoft.com/en-us/library/512aeb7t(VS.80).aspx
http://msdn.microsoft.com/en-us/library/ms379564(VS.80).aspx#csharp_generics_topic1
Object
。这样做有性能优势,但也使您的代码更易读、易于维护且更少出错。我这里有一个使用案例,来自于LinkedIn Learning的讲师Matt Milner。它可能有点啰嗦,不像你要求的那么简单,但我发现它对于深入了解泛型为什么必要非常有用。
假设你有这个方法:
static void Swap(object first, object second)
{
object temp = second;
second = first;
first = temp;
}
你可以这样使用它:
int x = 5, y = 7;
Swap(x, y);
System.Console.WriteLine($"X: {x} and Y: {y}");
输出结果为:
X: 5 and Y: 7
这里没有交换。为什么?因为:
int
是值类型。int
类型视为对象,但这涉及到装箱和拆箱。请记住,这是在内存层面上的昂贵操作。这个问题应该通过使用引用类型来解决吗?让我们试一试。
我们将使用一个自定义类,先前在某个库中定义:
var p1 = new Person
{
FirstName = "Matt",
LastName = "Milner"
};
var p2 = new Person
{
FirstName = "Amanda",
LastName = "Owner"
};
让我们交换它们。
Swap(p1, p2);
System.Console.WriteLine($"Person 1 is: {p1.FirstName}");
Amanda
,但我们得到的是:Person 1 is: Matt
为什么?通常我们会期望发生变化,因为类实例是按引用传递的。但这并不是事实,因为我们实际上是传递了地址的副本给temp
实例。
如果我们使用ref
修改方法呢?
ref
?static void Swap(ref object first, ref object second)
{
object temp = second;
second = first;
first = temp;
}
这应该允许我们不仅更改对象的部分,还可以更改它们所指向的内容。
Swap(ref p1, ref p2);
System.Console.WriteLine($"Person 1 is: {p1.FirstName}");
Person
的ref
转换为Object
的ref
。它们是完全不同的类型。int
、数组等)一起使用,而不会遇到这些问题。static void Swap<T>(ref T first, ref T second)
{
T temp = second;
second = first;
first = temp;
}
T
完全替代了Object
声明,并且允许告诉方法我们正在放置什么。就像这样:Swap<Person>(ref p1, ref p2);
Swap<int>(ref x, ref y);
System.Console.WriteLine($"Person 1: {p1.FirstName}");
System.Console.WriteLine($"X: {x} and Y: {y}");
输出结果为:
Person 1 is: Amanda
X: 7 and Y: 5
static void Swap(object first, object second); // No swap
static void Swap(ref object first, ref object second); // No swap
static void Swap<T>(ref T first, ref T second); // Swap
注意:我仍然需要自己检查为什么引用类型会以这种方式表现,据我所知,这与对象内存地址是如何传递、引用和复制有关。在大多数情况下,我只是按照Matt Miller的解释进行了跟随。