C#学习札记——其他

C#学习笔记——其他

运算符重载

C#运算符被定义为使用预定义类型作为操作数来工作。如果面对一个用户定义类型,运算符完全不知道如何处理它。运算符重载运行定义C#运算符操作自定义类型的操作数。

运算符重载只能用于类和结构。

为类或结构重载一个运算符x,可以声明一个名称为operator x的方法并实现它的行为。

一元运算符的重载方法带一个单独的class或struct类型的参数。

二元运算符的重载方法带两个参数,其中至少有一个必须是class或struct类型。

 

运算符重载方法必须被声明为:

带static和public两个修饰符。

类和结构的成员,该类或结构是它的一个操作数。

 

运算符重载的限制:

可重载的一元运算符:+,-,!,~,++,--,true,false

可重载的二元运算符:+,-,*,/,%,&,|,^,<<,>>,==,!=,>,<,>=,<=

 

递增和递减运算符是可重载的。但和预定义转换不同,重载运算符的前置和后置之前没有区别。

运算符重载不能做如下事情:

创建新运算符。

改变运算符的语法。

重新定义运算符如何让处理预定义类型。

改变运算符的优先级或结合性。

 

例:

 

class LimiterInt{

    const int MaxValue=100;

const int MinValue=0;

public static LimiterInt operator -(LimiterInt x)

{

   LimiterInt li=new LimiterInt();

li.TheValue=0;

return li;

}

public static LimiterInt operator -(LimiterInt x,LimiterInt y)

{

   LimiterInt li=new LimiterInt();

li.TheValue=x.TheValue-y.TheValue;

return li;

}

public static LimiterInt operator +(LimiterInt x,LimiterInt y)

{

   LimiterInt li=new LimiterInt();

li.TheValue=x.TheValue+y.TheValue;

return li;

}

 

        private int _TheValue=0;

public int TheValue

{

   get{return _TheValue;}

set

{

   if(value<MinValue)

   _TheValue=0;

else

   _TheValue=value>MaxValue?MaxValue:true;

}

}

 

}

 

 

class Program

{

    static void main(){

        LimiterInt li1=new LimiterInt();

LimiterInt li2=new LimiterInt();

LimiterInt li3=new LimiterInt();

li1.TheValue=10;li2.TheValue=26;

li3=-li;

Console.WriteLine("-{0}={1}",li1.TheValue,li3.TheValue);

li3=li2-li1;

Console.WriteLine("{0}-{1}={2}",li2.TheValue,li1.TheValue,li3.TheValue);

li3=li2+li1;

Console.WriteLine("{0}+{1}={2}",li2.TheValue,li1.TheValue,li3.TheValue);

    }

}





typeof运算符
typeof运算符返回作为它的参数的任何类型的System.Type对象。通过这个对象,可以得到类型的特征。(对任何已知类型,只要一个System.Type对象。)
typeof运算符是一元运算符。
例:
Type是System命名空间中的一个类:
Type t=typeof(SomeClass)

不能重载typeof。

例:使用typeof运算符以获取类的信息,并打印出它的公有字段和方法的名称。
using System.Reflection;

class SomeClass
{
    public int Field1;
public int Field2;
public void Method1(){}
public int Method2(){}
}

class Program
{
    static void Main()
{
   Type t=typeof(SomeClass);
   FieldInfo[] fi=t.GetFields();
MethodInfo[] mi=t.GetMethods();
foreach(FieldInfo f in fi)
   Console.WriteLine("Field : {0}",f.Name);
foreach(MethodInfo m in mi)
   Console.WriteLine("Method : {0}",m.Name);
}
}


typeof运算符还被GetType方法调用,该方法对每个类型的每个对象都有效。
class SomeClass
{
    ...
}

class Program
{
    static void Main()
    {
        SomeClass s=new SomeClass();
        Console.WriteLine("Type s : {0}",s.GetType().Name);
    }
}





using语句

某些类型的非托管对象有数量限制或很耗费系统资源。重要的是在代码使用完成它们后,尽可能快地释放它们。using语句有助于简化该过程并确保这些资源被适当的处置。

 

资源是一个实现System.IDisposable接口的类或结构。System.IDisposable接口含有一个名称为Disposable的方法。

使用资源的阶段,有以下部分组成:

分配资源

使用资源

处置资源

 

如果正在使用的资源的那部分代码中产生一个意外的运行时错误,那么处置资源的代码可能得不到执行。

 

注意:using语句不同于using指令

 

 

资源的包装使用

using语句帮助减少意外的运行时错误带类的潜在问题,它整洁地包装了资源的使用。

有两种using语句。

第一种形式如下:

圆括号内的代码分配资源。

Statement是使用资源的代码。

using语句隐式产生处置该资源的代码。

using(ResourceType Identifier=Expression) Statement

 

意外的运行时错误称为异常。using语句处理资源与异常的方式与try语句相似。

using语句执行下列内容:

分配资源。

Statement放进try块。

创建资源的Dispose方法的调用,并把它放进finally块。

 

 

例:

下面的代码使用using语句两次,一次对名称为TextWriter的类,一次对名称为TextReader

类,它们都来自System.IO命名空间。两个类都实现了IDisposable接,这是using语句的要求。

    TextWriter资源打开一个文本文件来写,并向文件写一行。

    TextReader资源然后打开相同的文本文件,一行一行读取并显示它的内容。

    在两种情况中,using语句确保对象的Dispose方法被调用。

    还要注意到Main中的using语句和开始两行的using指令之间的区别。

using System;    // using指令,不是using语句

using System.io; // using指令,不是using语句

 

namespace UsingStatement

   class Program

   {

       static void Main( )

          {

        //using语句

                     using (TextWriter tw=File.CreateText("Lirncoln.txt"))

                     {

                        tw.WriteLine("Four score and seven years ago,...");

                     }

       // using语句

                     using (TextReader tr = File.OpenText('Lincoln.txt");

                     {

                            string InputString;

                            while(null!=(InputString=tr.ReadLine()))

                                   Console.WriteLine(InputString);

                     }

              }

       }

}

 

这段代码产生以下输出:

     Four score and seven years ago,...

 

 

多个资源和嵌套

using语句还可以被用于相同类型的多个资源,资源声明用逗号隔开。语法如下:

            只有一个类型  资源     资源

using ( ResourceType Id1=Expr1,Id2=Expr2,...)EmbeddedStatement

 

 

例如,在下而的代码中,每个using语句分配并使用两个资源。

static void Main()

{

       using (Textbjriter tw1 = File.CreateText("Lincoln.txt"),

                      tw2 = File.CreateText("Franklin.txt"))

       {

              twl.WriteLine("Four score and seven years ago, ...");

              tw2.WriteLine("Early to bed; Early to rise ... ");

       }

      

       using (TextReader tri = File.OpenText("Lincoln.txt"),

                     tr2 = File.OpenText("Franklin.txt")

       {

              string InputString;

              while (null != (InputString - tri.ReadLine("))

                     Console.blriteLine(lnputString) ;

              while (null != (InputString - tr2.ReadLine("))

                     Console . HriteLine(InputString) ;

       }

      

 

using语句还可以被嵌套。在下面的代码中,除了嵌套using语句以外,还要注意到没有必要对第二个using语句使用块,因为它仅由一条单独简单语句组成。

using ( TextWriter tw1 = File.CreateText("Lincoln.txt"))

{

       tw1.WriteLine("Four score and seven years ago, ... ");

       using ( TextWriter tw2 = File.CreateText("Franklin.txt"))  //嵌套语句

              tw2.WriteLine("Early to bed; Early to rise ...");         //简单语句

}

 

 

using语句的另一种形式

关键字        资源              使用资源

using (Expression) EmbeddedStatement

 

在这种形式中,资源在using语句之前声明。

TextWriter tw=File.CreateText("Lincoln.txt");    //声明资源

using(tw)    //using语句

    tw.WriteLine("Four score and seven years ago, ...");

    虽然这种形式也能确保对资源的使用结束后Dispose方法总是被调用,但它不能防止你在using语句已经释放了它的非托管资源之后使用该资源,把它留在一种不一致的状态。因此它提供了较少的保护,而且不推荐使用。




引用其他程序集

    在迄今为止所看到的所有程序中,大部分都声明并使用它们自己的类。然而,在许多项目中,你会想使用来自其他程序集的类或类型。这些其他的程序集可能来自BCL,或来自一个第三方主,或你自己创建了它们。这些程序集称为类库,而且它们的程序集文件的名称通常以.dll扩展名结尾而不是.exe扩展名。

    例如,假设你想创建一个类库,它包含可以被其他程序集使用的类和类型。一个简单库的源代码如下面示例中所示,它包含在名称为SuperLib.cs的文件中。该库含有单独一个名称为SquareWidget的公有类。

public class SquareWidget

{

    public double SideLength=0;

    public double Area

    {

           get{return SideLength*SideLength}

    }

}

 

 

   假设你还要写一个名称为MyWidgets的程序,而且你想使用SquareWidget类。程序的代码在一个名称为MyWidgets.cs的文件中,如下面的示例所示。这段代码简单创建一个类型SquareWidget的对象并使用该对象的成员。

using System;

class WidgetsProgram

{

    static void Main()

    {

        SquareWidget sq=new SquareWidget();//来自类库

              //未在当前程序集中声明

        sq.SideLength=5.0;//设置边长

        Console.WriteLine(sq.Area);//输出该区域

              //未在当前程序集中声明

}

 

    注意,这段代码没有声明类SquareWidget。相反,使用的是定义在SuperLib中的类。然而,当你编译MyWidgets裎序时,编译器必须知道你的代码在使用程序集SuperLib,这样它才能得到关于类SquareWidget的信息。要实现这点,需要给编译器一个到该程序集的引用,给出它的名称和位置。

    Visual Studio中,可以用下面的方法把引用添加到项目:

    选择Solution Explorer并在该项目名下找到Referenccs目录。References目录包含项曰使用的程序集的列表。

    右键点击Refcrcnces目录并选择Add Reference。有五个可以从中选择的标签页,允许你以不同的方法找到类库。

    对于我们的程序,选择Browse标签,浏览到包含SquareWidget类定义的DLL文件,并选择它。

    点击OK按钮,引用就被加入到项目了。

    在添加了引用之后,可以编译MyWidgets了。

 

 

 

mscorlib

Console类被定义在名称为mscorlib的程序集中,在名称为mscorlib.dll的文件里。然而,你不会看到这个程序集被列在References目录中。程序集mscorlib.dll含有C#类型以及大部分.NET语言的基本类型的定义。在编译C#程序时,它必须总是被引用,所以Visual Studio不把它显示在References目录中。

 

 

 

    现在假设你的程序已经很好地用SquareWidget类工作了,但你想扩展它的能力以使用一个名称为CircleWidget的类,它被定义在另一个名称为UltraLib的程序集中。MyWidgets的源代码看上去像下面这样。它创建一个SquareWidget对象和一个CircleWidget对象,它们分别定义在SuperLib中和UltraLib中。

class WidgetsProgram

{

    static void Main()

    {

        Squarehlidget sq = new SquareWid8et();       //来自 SuperLib

              ...

        CircleWidget circle = new CircIWidget();    //来自 UltraLib

              ...

       }

}

 

类库UltralLib的源代码如下面的示例所示。注意,除了类CircleWidget之外,就像库SuperLib,它还声明了一个名称SquareWidget的类。可以把UltraLib编译成一个DLL并加入到项目MyWidgets的引用列表中。

 

public class SquareWidget

{...}

public class CircleWidget

{

    public double Radius = 0;

    public double Area

    {

        get{...}

    }

}

    因为两个库都含有名称为SquareWidget的类,当你试图编译程序MyWidgets对,编译器产生一条错误消息,因为它不知道使用类SquareWidget的哪个版本。此即命名冲突。

 

 

命名空间

    MyWidgets示例中,由于你有源代码,你能通过在SuperLib源代码或UltraLib源代码中仅仅   改变SquareWidget类的名称来解决命名冲突。但是,如果这些类是由不同的公司开发的,而且你还不能拥有源代码会怎么样呢?假设SuperLib由一个名称为MyCorp的公司生产,UltraLibABCCorp公司生产。在这种情况下,如果你使用了任何有冲突的类或类型,你将不能把这两个库放在一起使用。

    你能想象得出,在你做开发的机器上含有数打不同的公司生产的程序集,很可能有一定数量的类名重复。如果你不能把两个程序集用在一个程序中,仅仅因为它们碰巧有共同的类型名,这将是一种耻辱。命名空间特征须帮助你避免这个问题。

    命名空间把一组类型分组在一起并给它们一个名称,称为命名空间名称。命名空间名称应该描述命名空间的内容并和其他命名空间名称相区别。

    下面展示了声明。个命名空间的语法。声明在大括号中间的所有类和其他类型的名称都是命名空间的成员。

 

  关键字    命名空间名

namespace SimpleNamespace

(

  TypeOeclarations

}

 

    现在假设在MyCorp公司的程序员改变该源代码如下面的示例所示。它现在有一个环绕类声明的命名空间了。注意关于命名空间名称的两个有趣的事情:

    命名空间可以包含前缀。

    公司名称在命名空间名称的开始。

namespace MyCorp.SuperLib

{

   public class SquareWidget

   {

        public double SideLength=o;

        public double Area

        {

            get { retum SideLength * SideLength;}

              }

       }

}

 

    MyCorp公司运给你更新的程序集时,你可以通过修改你的MyWidgets程序如下面所示来使用它。注意,不仅使用类名(由于它在两个类库之间不明确)和使用命名空间名称做类名的前缀,并使用句点把它们隔开。带有命名空间名称和类名的整体字符串被称为完全限定名。

    class WidgetsProgram

    {

           MyCorp.SuperLib.SquareWidget sq=new MyCorp.SuperLib.SquareWidget();

             

              CircleWidget circle = new CircleWidget();

              ...

       }

 

现在你在代码中显示指示了SquareWidgetSuperLib版本,编译器不会再区分类的问题了。

 

 

 

 

 

命名空间名称

如你所见,命名空间的名称可以包含创建该程序集的公司的名称。除了标识公司以外,该名称还用于帮助程序员获得定义在命名空间内的类型的种类的快速印象。

    关于命名空问名称的一些要点如下:

    命名空间名称可以是任何有效标识符。

    命名空间名称可以包括句点符号,用于把类型组织成层次。

    例如,表列出了一些在.NET BCL中的命名空间的名称。

      

       下面是建议的命名空问命名指南:

使用公司名开始命名空间名称。

在公司名之后跟着技术名称。

不要把命名空间命名为与类或类型相同的名称。

 

例如,Acme Widget公司的软件开发部门在下面三个命名空间中开发软件,如下面的代码所示:

AcmeWidgets.SuperWidget

AcmeWidgets.Media

AcmeWidgets .Games

namespace AcmeWidgets.SuperWidget.SPDComponent

{

    class SPDBase...

       ...

}

 

 

命名空间的补充

    关于命名空间,有其他几个要点应该知道:

        在命名空间内,每个类型名必须有别于所有其他类型。

        命名空间内的类型称为命名空间的成员。

        一个源文件可以包含任意数日的命名空间声明,可以顺序也可以嵌套。

             

 

注意,尽管命名空问内含有几个共有的类名,它们被它们的命名空间名称区分开来。

 

    .NET框架BCL提供了数千个已定义的类和类型以供在生成你的程序时选择。为了帮助组织这组有用的功能,相关功能的类型被声明在相同的命名空间里。BCL使用超过100个命名空间来组织它的类型。

 

 

 

命名空间跨文件延伸

命名空间不是封闭的。这意味着可以在该源文件的后面或另一个源文件中再次声明它,以对它增加更多的类型声明。

 

 

嵌套命名空间

    一个命名空间可以是另一个命名空间的成员。这个成员被称为嵌套的命名空间。嵌套命名空间允许你创建类型的概念层次。

    虽然嵌套的命名空间是外层命名空间的成员,但它的成员不是外层命名空间的成员。人们最初有对嵌套的命名空间的误解是既然嵌套的命名空间在外层命名空间内部,那么嵌套命名空间的成员必须是外层命名空间昀子集。这是不对的,这些命名空间是分离的。

    有两种方法声明一个嵌套的命名空间,如下所示。

 

原文嵌套:可以把命名空间的声明放在一个封装的命名空间声明体内部,从而创建一个嵌套的命名空间。

namespace MyNamespace

{

    class MyClass{...}

       namespace OtherNs

       {

           class OtherClass{...}

       }

}

分离的声明:也可以为嵌套命名空间创建分离的声明,但必须在声明中使用它的完全限定名称。

namespace MyNamespace

{

    class MyClass{...}

}

namespace MyNamespace.OtherNs

{

    class OtherClass{...}

}

 

 

using指令

    完全限定名可能相当长,在代码中通篇使用它们会变得十分乏味。然而,有两个编译指令,可以使你避免不得不使用完全限定名:using命名空间指令和using别名指令。

    关于using指令词的两个要点如下:

        它们必须放在源文件的顶端,在任何类型声明之前。

        它们应用于当茼源文件中的所有命名空间。

             

using命名空间指令

    using命名空间指令通知编译器你将要使用来自某个指定命名空间的类型。然后你可以继续,并使用简单类名而小必全路径修饰它们。

 

当编译器遇到一个不在当前命名空问的名称时,它检查在using命名空间指令中给出的命名空间列表,并把该未知名称加到列表中的第一个命名空间后面。如果结果完全限定名称匹配了这个程序集或引用程序集中的一个类,编译器将使用那个类。如果不匹配,那么它试验列表中下一个命名空间。

    using命名空间指令由关键字using跟着一个命名空间标识符组成。

 

关键宇 命名空间的名称

using System;

 

    WriteLine方泫是类Console的成员,在System命名空间中。

    例如,下面的代码在第一行使用using命名空间指令以描述该代码使用来自system命名空问

类或其他类型。

using System;//命名空间指令

...

System.Console.WriteLine("This is text l");//使用完全限定名称

Console.WriteLine("This is text 2");//使用指令

 

 

using别名指令

    using别名指令允许起一个别名给:

    命名空间

    命名空间内的一个类型

    例如,下面的代码展示了两个using别名指令的使用。第一个指令告诉编译器标识符Syst是命名空间system的别名。第二个指令表明标识符SC是类System.Console的别名。

    关键宇  别名   命名空间

    using Syst = System;

    using SC = System.Console;

    关键宇 别名    

下面的代码使用这些别名。在Main中三行代码都调用System.Console.WriteLine方法。

Main的第一条语句使用命名空间(System)的别名。

第二条语句使用该方法的完全限定名。

第三条语句使用类(Console)的别名。

using Syst = System;    //using别名指令

using SC = System.Console;    //using别名指令

 

namespace MyNamespace

{

    class SomeClass

    {

        static void Main()

        {

            Syst.Console.WriteLine("Using the namespace alias.");//命名空间的别名

            System.Console.HriteLine("Using fully qualified name.");

            SC.HriteLine("Using the type alias");  //类的别名

        }

    }

}

 

 

程序集的结构

    程序集不包含本地机器代码,而是公共中间语言代码。它还包含实时编译器(JIT)在运行时转换CIL到本机代码所需的一切,包括对它所引用的其他程序集的引用。

程序集的文件扩展名通常为.exe.dll

    大部分程序集由一个单独的文件构成。

    程序集的清单包含:

              程序集名称标识符。

              组成程序集的文件列表。

              一个指示程序集中内容在哪里的地图。

              关于引用的其他程序集的信息。

    类型元数据部分包含该程序集中定义的所有类型的信息。这些信息包含关于每个类型要知道的所有事情。

    CIL部分包含程序集的所有中间代码。

    资源部分是可选的,但可以包含图形和语言资源。