c#1所搭建的核心基础之类型系统的特征

类型系统的特征简介

几乎每种编程语言都有某种形式的一个类型系统。类型系统大致被分为:强/弱,安全/不安全,静态/动态,显式/隐式等类型。

c#在类型系统世界中的位置

c#1的类型系统是静态的、显式的和安全的

静态类型和动态类型

c#是静态类型的:每个变量都有一个特定的类型,而且该类型在编译时是已知的。只有该类型已知的的操作才被允许。

例子:object o="hello";Console.WriteLine(o.Length);//报错

         object o="hello";Console.WriteLine(o.ToString().Length);//5

与静态类型对应的是动态类型,动态类型的的实质是变量中含有值,但那些值并不限定于特定的类型,所以编译器不能执行相同形式的检查。相反,执行环境试图采取一种合适的方式理解引用值的给定表达式。动态类型需要在执行代码时来动态检查类型。

例子(项目中例子部分代码):dynamic d = JObject.Parse(actionModel.jsonEntity);

            Term term=new Term();

            term.BeginTime = d.BeginTime;
            term.Enabled = d.Enabled;
            term.EndTime = d.EndTime;
            term.TermName = d.TermName;
            term.TermType = d.TermType;

显式类型和隐式类型

显式类型必须在声明中显式的声明。隐式类型则允许编译器根据变量的用途来推断变量的类型。

类型安全和类型不安全

描述类型安全系统的最简单方法就是描述它的对立面。有的语言允许做一些非常不正当的操作。在恰当的时候功能很强大,但是如果使用不当就会搬起石头砸自己的脚。滥用类型系统就属于这种情况。

c#是安全类型系统。在编译时,如果编译器发现这种转换实际是不可行的,就会触发一个编译时错误。另外如果理论上允许,但在执行时发现不正确,CLR也会抛出一个异常。

c#1的类型系统在什么时候不够用

集合强和弱

.net1.1内建了以下三种类型:

  • 数组 强类型 内建到语言和运行时中
  • System.Collections 命名空间中的弱类型集合
  • System.Collections.Specialized命名空间中的强类型集合

数组是强类型的。所以在编译时,不可能将string[]的一个元素设置成一个FileStream。然而,引用类型的数组也支持协变,只要元素的类型之间允许这样的转换,就能隐式将一种数组类型转换成另一种类型。执行时会进行检查,以确保类型有误的引用不会被存储下来。

例子:string[] strings=new string[5];

         object[] objects=strings;

         objects[0]=new button();

运行代码,会抛出ArrayTypeMismatchException 异常。这是由于从string[]转换成object[]会返回原始引用-无论strings或者objects都引用同一个数组。数组本身知道他是一个字符串数组,所以会拒绝存储非字符串的引用。数组协变有时候会派上用场,但代价是一些类型安全性在执行时才能实现,而不能在编译时实现。

让我们把它和弱类型的集合如ArrayList和HashTable的情况对比。这些集合的API定义键和值的类型都是object.例如,写一个ArrayList方法时,没有办法保证在编译时,调用者会传入一个字符串列表。可以把这个要求写入文档。只要将列表中的每个元素都强制转换为string,运行时类型安全性就会帮你强制使这个限制生效。但是,这样不会获得编译时的类型安全性。同样,如果返回一个ArrayList,可以再文档中指出他只包含字符串。但是调用者必须相信你说的是实话,而且在访问列表元素时必须插入强制类型转换。

最后看看强类型集合,如StringCollection.这些集合提供了一个强类型的API。所以,如果接受一个StringCollection作为参数或者返回值,可以肯定它只包含string.另外在取回集合元素时,不需要进行强制类型转换。这听起来似乎很理想,但有两个问题。首先,它实现了IList,所以仍然可以为它添加非字符串的对象,虽然运行时会失败。其次,它只能处理字符串。还有一些专门集合,但它们包括的范围不是很大。例如CollectionBase类型,可以用它创建你自己的强类型集合,但是那意味着要为每种元素类型都创建一个新集合,所以同样不理想。

缺乏协变返回类型

ICloneable 是框架中最简单的接口之一。它只有一个Clone方法,该方法返回调用方法的那个对象的一个副本。暂时不讨论这应该是一个深复制还是一个浅复制,先看看Clone方法的签名:object clone()

这是一个非常简单的签名,就像刚才所说,改方法应返回调用方法的那个对象的一个副本。这意味着他需要返回同类型的一个对象或至少兼容类型的一个对象。用一个覆盖方法的签名更准确地描述该方法实际返回的东西,应该是讲得通的。例如,在Person类中,像下面这样实现ICloneable接口是不错的选择:

public Person Clone();

这应该破坏不了任何东西,代码期待的旧的对象仍然能够正常工作。这个特性称为返回类型的协变。但遗憾的是,接口实现和方法覆盖不支持这一特性。对于接口来说,正常的解决方法是使用显式接口实现来获得预期结果。

Public Person Clone()

{

[Implemention goes here]

}

object ICloeeable.Clone()

{

return Clone();

}

这样以来,任何代码为一个表达式调用Clone时,如果编译器知道这个表达式的类型是Person ,就会调用顶部方法;如果表达式只是ICloneable,就会调用底部方法。这样虽然可行,但是太别扭了。参数也存在类似问题。假定一个接口方法或者一个虚拟方法,其签名是void process(string x),那么在实现或者覆盖这个方法时,使用一个放宽类限制的签名应该是合乎逻辑的,例如void process(object x).这称为参数类型的逆变性。但是和返回类型协变性一样,参数类型的逆变性也是不支持的。那么如何解决?对于接口,解决方案是一样的,同样是进行显示接口的实现。对于虚方法,解决方法则是进行普通的方法重载。虽然不是大问题,但着实令人烦恼。

拓展:深复制和浅复制

浅复制

可以使用受保护的方法System.Object.MemberwiseClone()进行浅复制。

public class Cloner{

pubilc int var;

public Cloner(int newVal)

{

var =newVal;

}

public object GetCopy()

{

 return MemberwiseClone();

}

}

使用浅复制的问题

假定有引用类型的字段,而不是值类型的字段:

public class Content{

pubic int val;

}

public  class Cloner{

public Content MyContent=new Content();

public Cloner(int newVal)

{

MyContent.val=newVal;

}

public object GetCopy()
{

return MemberwiseClone();

}

}

此时通过GetCopy()方法得到的浅复制包括一个字段,它引用的对象与源对象相同。下面的代码使用这个Cloner类来说明浅复制引用类型的结构。

Cloner MySource=new Cloner(5);

Cloner MyTarget=(Cloner)MySource.GetCopy();

Console.WriteLine("MyTarget.MyContent.val={0}",MyTarget.MyContent.Val);

MySource.MyContent.val=2;

Console.WriteLine("MyTarget.MyContent.val={0}",MyTarget.MyContent.Val);

上述结果如下:

MyTarget.MyContent.val=5;

MyTarget.MyContent.val=2;

为了解决这个问题需要执行深复制

修改上面的类

public class Content{

pubic int val;

}

public class Cloner:ICloneable{

public Content MyContent=new Content();

public Cloner(int newVal)

{

MyContent.val=newVal;

}

public object clone()
{

Cloneer clonedCloner=new Cloner(MyContent.Val);

return clonedCloner;

}

}

上述结果如下:

MyTarget.MyContent.val=5;

MyTarget.MyContent.val=5;

 注:整理自《深入理解c#》,《c#入门经典》

原文地址:https://www.cnblogs.com/wjcnet/p/3395896.html