1.默认值表达式

如果已经明确了要处理的类型,也就知道了它的“默认”值。不知道要引用的类型,就不能直接指定默认值。不能使用null,因为它可能不是一个引用类型,不能使用0,因为它可能不是数值类型。虽然很少需要用到默认值,但它偶尔还是有用的。Dictionary<TKey,TValue>就是一个好的例子,它有个TryValue方法,它的作用有点儿像对数值类型进行处理的TryParse方法:他用一个输出参数来接收你打算获取的值,用一个Boolean返回值显示它是否成功。这意味着方法必须用TValue类型的值来填充输出参数。请记住,输出参数必须在方法正常返回之前赋值。

为了满足这方面的要求,c#2提供了默认值表达式。虽然c#语言规范没有说他是一个操作符,但可以把它看做是与typeof相似的操作符,只是返回值不一样罢了。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace 默认值表达式
{
class Program
{
static int CompareToDefault<T>(T value)
where T : IComparable<T>
{
return value.CompareTo(default(T));
} static void Main(string[] args)
{
Console.WriteLine(CompareToDefault("x"));
Console.WriteLine(CompareToDefault());
Console.WriteLine(CompareToDefault());
Console.WriteLine(CompareToDefault(-));
Console.WriteLine(CompareToDefault(DateTime.MinValue));
}
}
}

运行结果:

在上述代码中,我们为泛型方法使用了3种不同的类型:string,int和DateTime. CompareToDefault方法规定只能使用实现了IComparable<T>接口的类型作为参数,这样才能为传入的值调用ComparTo<T>.传入的值要和类型的默认值进行比较。string是引用类型,默认值是null—根据有关CompareTo的文档,所有引用类型的值都要大于null,所以第一个输出的结果是1,随后三行和int的默认值进行比较,显示int的默认值是0.最后一行输出0,显示了DateTime.MinValue就是DateTime的默认值。

如果传递的参数是null,上述代码会抛出NullReferenceException异常。

2.直接比较

虽然上述代码演示了如何进行比较,但我们并不是总是愿意限制我们自己的类型来实现IComparable<T>或者它的姊妹接口IEquatable<T>,后者提供了一个强类型的Equals(T)方法,以弥补所有类型都具备的Equals(object)的不足。如果没有接口允许我们访问一些额外的信息,那么我们能做的事情就很少了。只能调用Equals(object)。如果要比较的值时值类型,它会造成装箱。

如果一个类型是未约束的,就可以使用==和!=操作符,但只能将该类型的值与null进行比较。不能直接比较两个T类型的值(会报错,无法通过编译),如果类型实参是一个引用类型,会进行正常的引用比较。如果为T提供的类型实参是一个非可空值类型,与null进行比较总是不相等(这样一来,JIT编译器就可以移除这个比较)。如果类型实参是可空值类型,那么就会自然而然的与类型的空值进行比较。

如果一个类型参数被约束成值类型,就完全不能使用==和!=。如果被约束成引用类型,那么具体执行的比较将完全取决于类型参数被约束成什么类型。如果它只是一个引用类型,那么执行的是简单的引用比较。如果被进一步约束成继承自某个重载了==和!=操作符的特定类型,就会使用重载运算符。但要注意,假如调用者指定的类型实参恰巧也进行了重载,那么这个重载操作符是不会使用的。

using System.Text;
using System.Threading.Tasks; namespace 直接比较实现
{
class Program
{
static bool AreReferencesEqual<T>(T first, T second)
where T:class
{
return first == second;
}
static void Main(string[] args)
{
string name = "Joy";
string intro1 = "My name is "+name;
string intro2 = "My name is "+name;
Console.WriteLine(intro1==intro2);
Console.WriteLine(AreReferencesEqual(intro1,intro2));
}
}
}

 运行结果为:

虽然string 重载了==,但在执行的比较中是不会用这个重载的。基本上,在说编译AreReferencesEqual<T>时,编译器根本不知道有哪些重载可用,就好比传入的只是object类型的参数。

并非只有操作符才有这个问题,遇到泛型类型时,编译器会在编译未绑定时就解析好所有方法重载,而不是等到执行时,才去为每个可能的方法调用重新考虑是否存在更具体的重载。例如,Console.WriteLine(default(t));这个语句总是被解析成Console.WriteLine(object object),即使为T传递的类型恰好就是string,也不会调用Console.WriteLine(string value),这好比普通方法重载是发生在编译时,而不是执行时。

需要对值进行比较时,有两个相当有用的类,他们是EqualityComparer<T>和Comparer<T>,两者都位于System.Collection.Generic命名空间中。他们分别实现了IEqualityComparer<T>(适合对字典进行比较和哈希处理)和IComparer<T>(适合排序)。这两个类的Default属性能返回一个实现,能为特点的类型采取正确的比较操作。

说明:泛型比较接口  共有四个主要的泛型接口可用于比较。IComparer<T>和IComparable<T>用于排序(判断某个值是小于、等于还是大于另一个值),而IEqualityComparer<T>和IEquatable<T>通过某种标准来比较两个项的相等性,或查找某个项的散列(通过相等性方式匹配)

如果换一种方式来划分这四个接口,IComparer<T>和IEqualiyComparer<T>的实例能比较两个不同的值,而IComparer<T>和IEquatable<T>的实例则可以比较它们本身和其他值。

3.一个完整的比较实例,表示一对值

这是一个完整的实例,它实现了一个有用的泛型类型,也就是一个Pair<T1,T2>,用于容纳两个值,类似键值对,但这两个值之间没有任何关系。

除了提供属性来访问值本身之外,我们还覆盖了Equals和GetHashCode方法,从而使这个类型的实例能很好的作为字典中的键来使用。

pair<T1,T2>类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace 表示一对值的泛型类
{
public sealed class Pair<T1, T2> : IEquatable<Pair<T1, T2>>
{
private static readonly IEqualityComparer<T1> FirstComparer = EqualityComparer<T1>.Default;
private static readonly IEqualityComparer<T2> SecondComparer = EqualityComparer<T2>.Default;
private readonly T1 first;
private readonly T2 second;
public Pair(T1 first, T2 second)
{
this.first = first;
this.second = second;
}
public T1 First { get { return first; } }
public T2 Second { get { return second; } }
public bool Equals(Pair<T1, T2> other)
{
return other != null && FirstComparer.Equals(this.First, other.First) && SecondComparer.Equals(this.Second, other.Second);
}
public override bool Equals(object obj)
{
return Equals(obj as Pair<T1,T2>);
}
public override int GetHashCode()
{
return FirstComparer.GetHashCode(first) * + SecondComparer.GetHashCode(second);
}
}
}

Pair<T1,T2>辅助类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace 表示一对值的泛型类
{
public static class Pair
{
public static Pair<T1, T2> Of<T1, T2>(T1 first, T2 second)
{
return new Pair<T1, T2>(first,second);
}
}
}

主体方法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 表示一对值的泛型类
{
class Program
{
static void Main(string[] args)
{
Pair<string, string> pair1 = new Pair<string, string>("hello","world");
Pair<string, string> pair2 = new Pair<string, string>("hello", "world");
Pair<int, string> pair3 = new Pair<int, string>(,"hello world");
bool c = pair1.Equals(pair2);
bool d = pair2.Equals(pair3);
System.Console.WriteLine(c);
System.Console.WriteLine(d);
System.Console.WriteLine(pair2.GetHashCode()); }
}
}

运行结果

c# in depth之泛型的实现的更多相关文章

  1. c# in depth之泛型的类型约束详细

    类型约束 1.引用类型约束 这种约束(表示成T:class,必须是为类型参数指定的第一个约束)用于确保使用的类型实参是引用类型,这可能是任何类,接口,数组,委托或者已知是引用类型的另一个类型参数. 例 ...

  2. c# in depth 之泛型实参的类型推断

    调用泛型方法时,指定类型实参常常会显得很多余.为简化工作,c#2编译器被赋予了一定的“智能”,让你在调用方法时,不需要显式声明类型实参. 在深入讨论这个主题之前,必须强调一下:类型推断只适用于泛型方法 ...

  3. C# 泛型List用法

    C# List Examples by Sam Allen - Updated September 6, 2009 Problem. You have questions about the List ...

  4. java泛型学习(一)

    泛型也叫做参数化类型,顾名思义的去理解,就是把类型作为一个参数.类比方法的传参,我们举个例子. class A{ public void getX(int x){ System.out.println ...

  5. 一起学 Java(三) 集合框架、数据结构、泛型

    一.Java 集合框架 集合框架是一个用来代表和操纵集合的统一架构.所有的集合框架都包含如下内容: 接口:是代表集合的抽象数据类型.接口允许集合独立操纵其代表的细节.在面向对象的语言,接口通常形成一个 ...

  6. .NET面试题系列[8] - 泛型

    “可变性是以一种类型安全的方式,将一个对象作为另一个对象来使用.“ - Jon Skeet .NET面试题系列目录 .NET面试题系列[1] - .NET框架基础知识(1) .NET面试题系列[2] ...

  7. C#4.0泛型的协变,逆变深入剖析

    C#4.0中有一个新特性:协变与逆变.可能很多人在开发过程中不常用到,但是深入的了解他们,肯定是有好处的. 协变和逆变体现在泛型的接口和委托上面,也就是对泛型参数的声明,可以声明为协变,或者逆变.什么 ...

  8. 编写高质量代码:改善Java程序的151个建议(第7章:泛型和反射___建议106~109)

    建议106:动态代理可以使代理模式更加灵活 Java的反射框架提供了动态代理(Dynamic Proxy)机制,允许在运行期对目标类生成代理,避免重复开发.我们知道一个静态代理是通过主题角色(Prox ...

  9. 6.在MVC中使用泛型仓储模式和依赖注入实现增删查改

    原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pat ...

随机推荐

  1. DDL 和DML 区别

    DML(Data Manipulation Language)数据操纵语言: 适用范围:对数据库中的数据进行一些简单操作,如insert,delete,update,select等. DDL(Data ...

  2. BufferedInputStream 源码分析

    一.简介 BufferedInputStream会缓存一部分数据(默认8K),这个函数的作用就是读取更多的数据到缓存,必要的时候会扩大缓存的内容. 在该类中有几个重要的标志位:markpos,pos, ...

  3. POJ 3675 Telescope 简单多边形和圆的面积交

    这道题得控制好精度,不然会贡献WA  QAQ 还是那个规则: int sgn(double x){ if(x > eps) return 1; else if(x < - eps) ret ...

  4. Cognos 图表用图片取代”没有数据显示”

    在Cognos中做出来报表展示的时候因为没有数据感觉显示“没有可用数据”感觉很不美观.所以想用一张图片代替. 在图表的属性里面有一个“无数据内容”,点击打开之后有三个选项: 默认就是显示“没有可用数据 ...

  5. 基于visual Studio2013解决算法导论之013基数排序

     题目 基数排序 解决代码及点评 #include <stdio.h> #include <stdlib.h> #include <malloc.h> #in ...

  6. 一起学习CMake – 02

    本节介绍如何用CMake来设置软件的版本号 在<一起学习CMake - 01>中我们看到了如何用CMakeLists.txt来构建一个最简单的工程,这一节里我们一起来看看如何用CMake对 ...

  7. 评侯捷的<深入浅出MFC>和李久进的<MFC深入浅出>

    侯捷的<深入浅出mfc>相信大家都已经很熟悉了,论坛上也有很多介绍,这里我就不多说了. 而李久进的<mfc深入浅出>,听说的人可能就少得多.原因听说是这本书当时没有怎么宣传,而 ...

  8. 从C到C++,C语言与C++的关系

    C语言是1972年由美国贝尔实验室的D.M.Ritchie研制成功的.它不是为初学者设计的,而是为计算机专业人员设计的.大多数系统软件和许多应用软件都是用C语言编写的.但是随着软件规模的增大,用C语言 ...

  9. 配置Jenkins的slave节点的详细步骤适合windows等其他平台(转)

    @  新建一个slave节点在Jenkins服务器上 1,进入Jenkins的主界面,进入“Manage Jenkins” 页面: 2,点击如下图中的“Manage  Nodes”: 3,进入页面后点 ...

  10. SAP 金税接口介绍

    一.金税发票与SAP系统发票的税额差异分析 1.1 金税系统中的税额说明 国内企业销售产品给国内客户时,正常产品须要缴纳17%的增值税,而金税(Golden Tax)系统就是用来出具纸面的增值税发票的 ...