作为C#的初学者,泛型的概念可能让人感到畏惧。如果能穿越时空,会将这篇文章发送给过去的自己。换句话说,这篇文章的目标读者是初学者,希望它能帮助许多刚开始学习C#的人揭开泛型的神秘面纱。
将从最简单的例子开始,然后逐步增加复杂性。
这是一个非常简单的方法。它返回一个整数。
public int ReturnsOneInt()
{
return 1;
}
但是,如果想从这个方法中返回多个整数呢?方法一次只能返回一个值,所以如果想返回两个整数,就需要像这样修改它:
public class TwoInts
{
public int Int1 { get; set; }
public int Int2 { get; set; }
}
public TwoInts ReturnsTwoInts()
{
return new TwoInts() { Int1 = 1, Int2 = 2 };
}
通过将两个整数值打包到一个对象实例中,可以返回任意数量的值。但是,如果还需要从另一个方法中返回一个整数和一个字符串呢?现在就需要创建一个像这样的类:
public class IntAndString
{
public int Int1 { get; set; }
public string String1 { get; set; }
}
很快,代码中就会充斥着这些小类。但是有一个更好的方法。.NET内置了一个名为Tuple的泛型类。可以像这样使用它:
public Tuple<int, int> ReturnsTwoInts()
{
return new Tuple<int, int>(1, 2);
}
public Tuple<int, string> ReturnsIntAndString()
{
return new Tuple<int, string>(1, "two");
}
这就是泛型之所以有用的原因。不是为每个特定场景创建一个类,而是创建一个泛型类,可以在许多场景中使用。
为了演示这里发生了什么,将创建自己的tuple类。代码如下:
public class MyTuple<TypeParameter1, TypeParameter2>
{
public TypeParameter1 Value1 { get; set; }
public TypeParameter2 Value2 { get; set; }
public MyTuple(TypeParameter1 value1, TypeParameter2 value2)
{
Value2 = value2;
Value1 = value1;
}
}
在第一行,将看到类声明。这里唯一新的东西是两个类型参数。类型参数是传递给泛型类的参数,就像传递给方法的参数一样。但是,不是在圆括号()中传递参数,而是在<>中传递它们。
像这样传递类型参数到泛型类:
MyTuple<int, int> twoInts = new MyTuple<int, int>(1, 2);
在这里,传递了两个int类型作为类型参数,所以TypeParameter1和TypeParameter2都将是int。
现在看看第4行的构造函数。同样的类型参数被用来定义构造函数期望的类型,这就是为什么被允许将值1和2传递到构造函数中。两个public属性也使用类型参数来定义它们的类型,这允许将参数值设置为在构造函数中接收到的参数。
这也是为什么现在可以这样做:
int integerValue = twoInts.Value1;
integerValue = twoInts.Value2;
重要的是要理解twoInts的类型是MyTuple<int, int>。这可以通过这样做来证明:
MyTuple<int, int> anotherTwoInts = twoInts;
// COMPILES!
下一个例子将不会编译,因为不允许将一个变量设置为另一个变量,如果它们的类型不相同(注意第二个类型变量是string):
MyTuple<int, string> anotherTwoInts = twoInts;
// DOES NOT COMPILE!
让给tuple类添加一些功能。
public class MyTuple<TypeParameter1, TypeParameter2>
{
public TypeParameter1 Value1 { get; set; }
public TypeParameter2 Value2 { get; set; }
public MyTuple(TypeParameter1 value1, TypeParameter2 value2)
{
Value2 = value2;
Value1 = value1;
}
// 注意返回类型的类型参数已经被交换。
public MyTuple<TypeParameter2, TypeParameter1> SwapValues()
{
// 创建一个新的MyTuple实例,再次交换类型参数,以匹配方法本身的返回类型
return new MyTuple<TypeParameter2, TypeParameter1>(Value2, Value1);
}
}
现在添加了SwapValues()方法。它所做的就是返回一个新的MyTuple类的实例,两个值以及类型参数的位置都被交换了。重要的是要注意,正在使用创建tuple实例时发送的类型参数来定义SwapValues方法的返回类型。
再次,非常重要的是要理解这些泛型类实例的类型如何发挥作用。
这可能看起来不重要,但在继续之前,请确保理解为什么这个不会编译:
MyTuple<int, string> swapped = intAndString.SwapValues();
// does NOT compile
这个会编译:
MyTuple<int, string> swappedAgain = intAndString.SwapValues().SwapValues();
另一个有趣的是,能够在另一个泛型类中创建一个泛型类的实例。这是因为可以将接收到的类型参数传递给其他泛型类。
例如,可以向MyTuple类添加这个方法:
public List<TypeParameter1> GetListOfValue1(int length)
{
// 创建要返回的列表
List<TypeParameter1> toReturn = new List<TypeParameter1>();
// 将Value1添加到列表中所需的次数
for (int i = 0; i < length; i++) toReturn.Add(Value1);
return toReturn;
}
这个新方法简单地创建了一个List,并用一定数量的Value1填充它,并返回列表。List<T>,就像自己的MyTuple<T1, T2>,是一个泛型类,所以发送类型参数。在GetListOfValue1()方法中,正在创建一个新的List<T>实例,将TypeParameter1作为List<T>所需的类型参数发送。
这是一个更复杂的例子:
public static List<MyTuple<T1, T2>> CombineLists<T1, T2>(List<T1> lst1, List<T2> lst2)
{
List<MyTuple<T1, T2>> toReturn = new List<MyTuple<T1, T2>>();
for (int i = 0; i < lst1.Count(); i++)
{
toReturn.Add(new MyTuple<T1, T2>(lst1[i], lst2[i]));
}
return toReturn;
}
这个方法接受两个列表作为参数,然后将列表中的项组合成一个包含元组的单个列表。注意,这两个列表必须长度相等,否则这将会失败。
类型参数在这里定义:
CombineLists<T1,T2>
两个类型参数T1和T2被创建,并在方法的其余部分中使用。上面定义的类型参数用于定义CombineLists方法的返回类型,该方法返回一个包含MyTuple对象的列表,这些对象依次接收T1和T2作为它们的类型参数。