您的位置:首页 > 编程语言 > C#

C#泛型特性总结

2015-10-01 18:16 597 查看

1.C#泛型的声明

泛型一个是数据类型抽象,就是一个类或者函数的模板。
范型可以实现接口和继承类,继承基类的类型可以是范型也可以是具体类型。
范型类的静态成员只能在一个实例中共享因为类型都不同。

声明,用泛型类型T:
public class List<T>
{
}

public delegate void Display<T>(T value)
{}
对泛型有更进一步的限定,必须实现一个接口或派生自父类,或者使用了特定的泛型类型,或者使用了多个泛型类型,例如:
public delegate void EventHandle<TEventArgs>(object sender, TEventArgs e)
{}
public delegate TOutput Convert<TInput, TOutput>(TInput from)
{}
public class SortedList<TKey, TValue>
{}


2.C#范型优点

1). 去掉装箱拆箱,性能更高更安全:实例化时候相比非范型的数据结构用引用类型有性能上面的优势,不需要装箱放入和拆箱强转的性能损耗,且访问时候类型安全。默认的数据结构是存储引入的,值类型传入时候就要装箱,值类型获取时候要强转就要拆箱,不断的装箱拆箱是很影响性能的。

2).泛型可以多语言相互调用: 和其它类型范型因为是CRL实例化的,所以一种语言定义范型类接口方法其它语言可以使用。
3). 共享引用类型泛型,更小代码:范型类型实例化时候引用只实例化一份范型代码,值类型实例化不同值类型范型代码。


3.使用泛型需要注意的事项

范型中注意要用default返回默认值。
范型约束where可以约束指定泛型类型是某种类或某种结构体,只能指定默认构造函数的约束不能指定其它形式的构造函数的约束,接口不能指定操作符重载。
where T:struct T是值类型
where T:class T是引用类型
where T:IFoo T必须实现接口
where T:Foo T必须继承类
where T:new() T必须有默认构造函数
where T1:T2 多个泛型,T1必须继承T2
范型接口,接口中可以使用范型限定,接口中的方法参数可以用范型类型,.net库中就要很多范型接口。


4.泛型约束实例

// 1.C#自动生成属性字段
// 2.泛型管理器,有where 实现接口约束
// 3.lock (this) 在private/private static 中更加安全。
// 4.foreach语句的表达形式
namespace Wrox.ProCSharp.Generics
{
public interface IDocument
{
string Title { get; set; }
string Content { get; set; }
}

public class Document : IDocument
{
// C#3.0以后会自动生成属性字段由CRL生成,例如.field private string '<Name>k__BackingField'
// 需要同时提供默认的get;set;访问器,实际开发中很多应用,简化代码。
public Document()
{
}
public Document(string title, string content)
{
this.Title = title;
this.Content = content;
}

public string Title { get; set; }
public string Content { get; set; }
}
}

using System;
using System.Collections.Generic;
namespace Wrox.ProCSharp.Generics
{
// 泛型管理器,有where 实现接口约束
public class DocumentManager<TDocument>
where TDocument : IDocument
{
private readonly Queue<TDocument> documentQueue = new Queue<TDocument>();
public void AddDocument(TDocument doc)
{
// 进入临界区
lock (this)
{
documentQueue.Enqueue(doc);
}// 离开临界区
}

// 只读属性
public bool IsDocumentAvailable
{
get { return documentQueue.Count > 0; }
}

public void DisplayAllDocuments()
{
// foreach语句的表达形式
foreach (TDocument doc in documentQueue)
{
Console.WriteLine(doc.Title);
}
}

public TDocument GetDocument()
{
// default将对值类型赋予默认值,引用类型赋予null
TDocument doc = default(TDocument);
// 1.lock 关键字可确保当一个线程位于代码的临界区时,另一个线程不会进入该临界区。
// 如果其他线程尝试进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
// lock 关键字在块的开始处调用 Enter,而在块的结尾处调用 Exit。

// 2.通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。
// 最佳做法是定义 private 对象来锁定, 或 private static 对象变量来保护所有实例所共有的数据
lock (this)
{
doc = documentQueue.Dequeue();
}
return doc;
}
}
}

using System;
namespace Wrox.ProCSharp.Generics
{
class Program
{
static void Main()
{
var dm = new DocumentManager<Document>();
dm.AddDocument(new Document("Title A", "Sample A"));
dm.AddDocument(new Document("Title B", "Sample B"));
dm.DisplayAllDocuments();
if (dm.IsDocumentAvailable)
{
Document d = dm.GetDocument();
Console.WriteLine(d.Content);
}
}
}
}

5.泛型容器优点和支持迭代器访问的容器的实现,yield 语句的使用

using System.Collections;
using System.Collections.Generic;
namespace Wrox.ProCSharp.Generics
{
public class LinkedList<T> : IEnumerable<T>
{
public LinkedListNode<T> First { get; private set; }
public LinkedListNode<T> Last { get; private set; }

public LinkedListNode<T> AddLast(T node)
{
var newNode = new LinkedListNode<T>(node);
if (First == null)
{
First = newNode;
Last = First;
}
else
{
Last.Next = newNode;
Last = newNode;
}
return newNode;
}

// 实现IEnumerable的获取真正迭代器的方法就可以了,真正迭代MoveNext交给IEnumerator<T>去处理
public IEnumerator<T> GetEnumerator()
{
LinkedListNode<T> current = First;

while (current != null)
{
yield return current.Value;
current = current.Next;
}
}

// 需要重写的IEnumerable.GetEnumerator方法
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

using System;
namespace Wrox.ProCSharp.Generics
{
class Program
{
static void Main()
{
// int类型,使用泛型的链表比Object类型的链表性能好,编译就可以发现错误
var list2 = new LinkedList<int>();
list2.AddLast(1);
list2.AddLast(3);
list2.AddLast(5);

// 调用端实现迭代,输出元素
foreach (int i in list2)
{
Console.WriteLine(i);
}

// string类型,使用泛型的链表比Object类型的链表性能好,编译就可以发现错误
var list3 = new LinkedList<string>();
list3.AddLast("2");
list3.AddLast("four");
list3.AddLast("foo");

foreach (string s in list3)
{
Console.WriteLine(s);
}

}
}
}


6.泛型方法和多种委托的实现

泛型方法可以有where约束,委托可以用泛型的等。
泛型方法可以被重载和具有多态性,泛型是在编译期间决定的使用了类型,所以在运行期间是不会再调整调用的多态泛型函数的。
using System;
namespace Wrox.ProCSharp.Generics
{
public interface IAccount
{
decimal Balance { get; }
string Name { get; }
}

public class Account : IAccount
{
public string Name { get; private set; }
public decimal Balance { get; private set; }

public Account(string name, Decimal balance)
{
this.Name = name;
this.Balance = balance;
}
}
}

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

namespace Wrox.ProCSharp.Generics
{
public static class Algorithm
{
public static decimal AccumulateSimple(IEnumerable<Account> source)
{
decimal sum = 0;
foreach (Account a in source)
{
sum += a.Balance;
}
return sum;
}

public static decimal Accumulate<TAccount>(IEnumerable<TAccount> source)
where TAccount : IAccount
{
decimal sum = 0;

foreach (TAccount a in source)
{
sum += a.Balance;
}
return sum;
}

public static T2 Accumulate<T1, T2>(IEnumerable<T1> source, Func<T1, T2, T2> action)
{
T2 sum = default(T2);
foreach (T1 item in source)
{
// Func<T1, T2, T2>委托,应该是前面T1,T2是函数参数,后面参数T2是返回值
sum = action(item, sum);
}
return sum;
}
}
}

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

namespace Wrox.ProCSharp.Generics
{
class Program
{
static void Main()
{
// 直接用容器的构造函数为容器增加元素
var accounts = new List<Account>()
{
new Account("Christian", 1500),
new Account("Stephanie", 2200),
new Account("Angela", 1800)
};

// 基础数据结构类型,可以直接赋值给指定类型的IEnumerable<Account>类型
decimal amount = Algorithm.AccumulateSimple(accounts);
Console.WriteLine("after Algorithm.AccumulateSimple: " + amount);

amount = Algorithm.Accumulate(accounts);
Console.WriteLine("after Algorithm.Accumulate: " + amount);
// 委托(item, sum) => sum += item.Balance,前面应该item和sum都是类似#define的宏定义,=>后面是返回值
amount = Algorithm.Accumulate<Account, decimal>(accounts, (item2, sum2) => sum2 += item2.Balance);
Console.WriteLine("after Algorithm.Accumulate<Account, decimal>: " + amount);
}
}
}

using System;
namespace Wrox.ProCSharp.Generics
{

public class MethodOverloads
{
public void Foo<T>(T obj)
{
Console.WriteLine("Foo<T>(T obj), obj type: {0}", obj.GetType().Name);
}

public void Foo(int x)
{
Console.WriteLine("Foo(int x)");
}

public void Bar<T>(T obj)
{
Foo(obj);
}
}

class Program
{
static void Main()
{
var test = new MethodOverloads();
// 用Foo(int x)
test.Foo(33);
// 用Foo<T>(T obj)
test.Foo("abc");
// 编译期间决定的,所以是用Foo<T>(T obj)
test.Bar(44);
}
}
}


7.泛型的协变和抗变

C#4.0引入的泛型变体的概念,就是泛型的类型是可变的。
协变要求实现的泛型的类型是子类,要变到没有实现泛型类型的父类,是很协调理所当然的。
抗变(逆变)要求实现泛型的类型是父类,要变到没有实现泛型类型的子类,是一种有点逆向的转换。这个
1)4.0之前泛型类型接口是不变的也即是泛型的类型没有父子类子分,在 .NET Framework 4中,变体类型参数仅限于泛型接口和泛型委托类型。
2)泛型接口或泛型委托类型可以同时具有协变和逆变类型参数。
3)变体仅适用于引用类型;如果为 Variant 类型参数指定值类型,则该类型参数对于生成的构造类型是不变的。
4)协变类型创建的是泛型类型子类对象,因此适用于作为函数的返回值。
5)抗变(逆变)类型创建的是泛型类型父类对象,因此适用于作为函数的传入参数。

从 .NET Framework 4 开始,某些泛型接口具有协变类型参数;例如:IEnumerable<T>、IEnumerator<T>、IQueryable<T> 和 IGrouping<TKey, TElement>。 由于这些接口的所有类型参数都是协变类型参数,因此这些类型参数只用于成员的返回类型。

// covariant 定义了泛型接口是协变的,实现泛型类型中的子类,可以将泛型类型子类赋值给泛型类型父类
public interface IIndex<out T>
// 派生类中不需要泛型的协变定义了
public class RectangleCollection : IIndex<Rectangle>
{
}
public static RectangleCollection GetRectangles()
{
// 每次都会返回一个新的对象
return new RectangleCollection();
}
// 本身是协变的,所以实现的是泛型的子类
IIndex<Rectangle> rectangles = RectangleCollection.GetRectangles();
// 协变类型,可以将子类泛型赋值给父类泛型,也就是泛型的协变
IIndex<Shape> shapes = rectangles;

// contra-variant,定义泛型类型是抗变的,实现泛型类型中的是父类,可以将泛型类型父类赋值给泛型类型子类
public interface IDisplay<in T>
{
void Show(T item);
}

//IDisplay<Shape> 泛型的类型是父类的对象
IDisplay<Shape> shapeDisplay = ShapeDisplay.GetShapeDisplay()/*new ShapeDisplay()*/;
// 抗变是泛型类型,可以用泛型类型父类对象赋值给泛型类型子类对象
IDisplay<Rectangle> rectangleDisplay = shapeDisplay;
rectangleDisplay.Show(rectangles[0]);


8.泛型结构和可空类型

泛型结构和泛型类一样,只是泛型结构没有泛型继承特性。
用一个Nullable<struct or base type name> typeName;就可以定义一个可空类型,因为数据库中或者xml中允许值类型为null类型,而C#中值类型必须有默认值,如果将值类型转换为引用会有装箱操作影响性能,如果用可空的泛型类型却因为确定了类型而不需要装箱操作可以很好的提高性能。可空类型使用直接用即可。
可空类型的声明有,?可以声明为可空类型:
Nullable<int> x1;
int ? x2;
对可以声明可空类型,操作时候:
x1 = 10;
x2 = x1;
if(!x1.HasValue)
{
x1 = null;
}

数据交互时候:
不可空类型直接转换为可空类型:
int y1 = 4;
int ? x1 = y1;

可空类型转换为不可空类型,可能会失败,需要进行强制转换:
int ? x1 = GetNullableType();
int y1 = (int)x1;
或??声明为合并符号,定义了默认值如果x1空那么y1 = 0:
int y1 = x1 ?? 0;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: