C#图解教程 第六章 深入理解类

深入理解类

类成员


前两章阐述了9种类成员中的两种:字段和方法。本章将会介绍除事件(第14章)和运算符外的其他类成员,并讨论其特征。

成员修饰符的顺序


字段和方法的声明可以包括许多如public、private这样的修饰符。本章还会讨论许多其他修饰符。多个修饰符一起使用时,它们需要怎么排序呢?

[特性] [修饰符] 核心声明

  • 修饰符
    • 修饰符,必须放在核心声明前
    • 多个修饰符顺序任意
  • 特性
    • 特性,必须放在修饰符和核心声明前
    • 多个特性顺序任意

例如,public和static都是修饰符,可以用在一起修饰某个声明。因为它们都是修饰符,所以顺序任意。下面两行代码是语义等价的:

public static int MaxVal;
static public int MaxVal;

实例类成员


类的每个实例拥有自己的各个类成员的副本,这些成员称为实例成员。 
改变一个实例字段的值不会影响任何其他实例成员中的值。 

class D
{public int Mem1;
}
class Progarm
{static void Main(){D d1=new D();D d2=new D();d1.Mem1=10;d2.Mem1=28;Console.WriteLine("d1={0},d2={1}",d1.Mem1,d2.Mem2);}
}

静态字段


除了实例字段,类还可以拥有静态字段

  • 静态字段被类的所有实例共享,所有实例访问同一内存位置。因此,如果该内存位置的值被一个实例改变了,这种改变对所有实例都可见。
  • 可以使用static修饰符将字段声明为静态
class D
{int Mem1;        //实例字段static int Mem2; //静态字段
}

例:静态字段演示

  • 因为Mem2是静态的,类D的两个实例共享单一的Mem2字段。如果Mem2被改变了,这个改变在两个实例中都能看到
  • 成员Mem1没有声明为static,所以每个实例都有自己的副本
class D
{int Mem1;static int Mem2;...
}
static void Main()
{D d1=new D();D d2=new D();...
}

从类的外部访问静态成员


静态成员可以使用点运算符从类的外部访问。但因为没有实例,所以必须使用类名。

类名↓
D.Mem2=5;↑成员名
静态字段示例
  • 一个方法设置两个数据成员的值
  • 另一个方法显示两个数据成员的值
class D
{int Mem1;static int Mem2;public void SetVars(int v1,int v2){Mem1=v1;Mem2=v2;}public void Display(string str){Console.WriteLine("{0}:Mem1={1},Mem2={2}",str,Mem1,Mem2);}
}
class Program
{static void Main(){D d1=new D(),d2=new D();d1.SetVars(2,4);d1.Display("d1");d2.SetVars(15,17);d2.Display("d2");d1.Display("d1");}
}

静态成员的生存期
  • 之前我们已经看到了,只有在实例创建后才产生实例成员,实例销毁后实例成员也就不在存在
  • 即使类没有实例,也存在静态成员,并可以访问
class D
{int Mem1;static int Mem2;...
}
static void Main()
{D.Mem2=5;Console.WriteLine("Mem2={0}",D.Mem2);
}

 
字段与类有关,与实例无关


静态成员即使没有类的实例也存在。如果静态字段有初始化语句,那么会在使用该类的任何静态成员之前初始化该字段,但没必要在程序执行的开始就初始化。

静态函数成员


  • 如同静态字段,静态函数成员独立于任何类实例。无需类实例就可以调用静态方法
  • 静态函数成员不能访问实例成员。只能访问静态成员

例:静态函数与静态字段

class X
{static public int A;static public void PrintValA(){Console.WriteLine("Value of A:{0}",A);}
}
class Program
{static void Main(){X.A=10;X.PrintValA();}
}

其他静态类成员类型


下表中为可以声明为static的类成员类型做了√标记


成员常量


成员常量类似本地常量,只是它被声明在类声明中而不是方法内。

class MyClass
{const int IntVal=100;//定义值为100的int类型常量
}
const double PI=3.1416; //错误:不能在类型声明之外

与本地常量类似,初始化成员常量的值在编译时必须是可计算的。

class MyClass
{const int IntVal1=100;const int IntVal2=2*IntVal1;//正确:因为IntVal的值在前一行已设置
}

与本地常量类似,不能在成员常量声明后给它赋值

class MyClass
{const int IntVal//错误:声明时必须初始化IntVal=100;     //错误:不允许赋值
}

与C和C++不同,在C#中没有全局变量。每个常量都必须声明在类型内。

常量与静态量


成员常量比本地常量更有趣,因为它们表现得像静态值。它们对类的每个实例都是“可见的”,而且即使没有类的实例也可以用。与真正的静态量不同,常量没有自己的存储位置,而是在编译时被编译器替换。常量类似C和C++中的#define。 
正因为常量在内存没有存储位置,所以它也不能作为左值(被赋值)。 
static静态量是有自己的存储位置的。

例:常量示例

class X
{public const double PI=3.1416;
}
class Program
{static void Main(){Console.WriteLine("pi={0}",X.PI);}
}

 

虽然常量成员表现得像一个静态量,但不能将常量声明为static

static const double PI=3.14;//错误:不能将常量声明为static

属性


属性代表类的实例或类中的一个数据项成员。使用属性看起来像写入或读取一个字段,它们语法相同。

与字段类似,属性有如下特征

  • 它是命名的类成员
  • 它有类型
  • 它可以被赋值和读取

然而和字段不同,属性是一个函数成员

  • 它不为数据存储分配内存
  • 它是执行代码

属性是指定的一组两个匹配的、称为访问器的方法

  • set访问器为属性赋值
  • get访问器从属性获取
属性声明和访问器
  • set访问器
    • 拥有一个单独的、隐式的值参,名称为value,与属性的类型相同
    • 拥有一个返回类型void
  • get访问器
    • 没有参数
    • 拥有一个与属性类型相同的返回类型

访问器的其他重点

  • get访问器的所有执行路径必须包含一条return语句,返回一个属性类型的值
  • 访问器set、get顺序任意,除这两个访问器外在属性上不允许有其他方法
属性示例

例:名为C1的类,包含一个名为MyValue的属性

  • 属性本身没有任何存储。访问器决定如何处理发进来的数据,以及什么数据发出去。示例中,属性使用TheRealValue字段作为存储
  • set访问器接受它的输入参数value,并把值赋给字段TheRealValue
  • get访问器只是返回字段TheRealValue的值
class C1
{private int TheRealValue;//字段:分配内存public int MyValue       //属性:不分配内存{set{TheRealValue=value;}get{return TheRealValue;}}
}

使用属性
  • 要写入属性,在赋值语句的左边使用属性名
  • 要读取属性,把属性名用在表达式中
  • 属性根据是写入还是读取,隐式调用访问器。(不能显示调用访问器)
int MyValue
{get{...}set{...}
}
...
MyValue=5;
z=MyValue;
属性和关联字段

正如下文C1示例中的。一种常见的方式是在类中将字段声明为private以封装字段,并声明一个public属性来控制从类的外部对该字段的访问。 
和属性关联的字段常被称为后备字段、后备存储。

例:使用public的MyValue来控制对private的TheRealValue的访问

class C1
{private int TheRealValue=10;//字段:分配内存public int MyValue          //属性:不分配内存{set{TheRealValue=value;}get{return TheRealValue;}}
}
class Program
{static void Main(){C1 c=new C1();Console.WriteLine("MyValue:{0}",c.MyValue);c.MyValue=20;Console.WriteLine("MyValue:{0}",c.MyValue);}
}

属性和它后备字段的命名有两种约定。 
约定一:属性使用Pascal大小写,字段使用Camel大小写。虽然这违反了“仅使用大小写区分不同标识符是坏习惯”。但胜在简单,有意义。 
约定二:属性使用Pascal大小写,字段使用Camel大小写并在开头加"_"号。

private int firstField;
public int FirstField
{get{return firstField;}set{firstField=value;}
}
private int _secondField;
public int SecondField
{get{return _secondField;}set{_secondField=value;}
}
执行其他计算

属性访问器不仅对后备字段传进传出数据。也可以执行任何计算。 
例:通过set属性访问器限制Hour的最大值为24

int Hour=12;
int MyValue
{set{Hour=value>24?24:value;}get{return Hour;}
}

上面示例中,演示的条件运算符,将在第8章详细阐述。 
条件运算符是一种三元运算符,计算问号前的表达式,如果表达式结果为true,则返回问号后第一个表达式,否则,返回冒号后的表达式。

只读和只写属性
  • 只有get访问器的属性称为只读属性。只读属性是一种安全的,把一项数据从类或类的实例中传出,而不允许太多访问方法
  • 只有set访问器的属性称为只写属性,只写属性是一种安全的,把一项数据从类的外部传入类,而不允许太多访问方法
  • 两个访问器中至少要定义一个

属性与公共字段

按照推荐的编码实践,属性比公共字段更好

  • 属性是函数型成员而不是数据成员,允许你处理输入和输出,而公共字段不行
  • 属性可以只读或只写,字段不行
  • 编译后的变量和编译后的属性语义不同
计算只读属性示例

例:类RightTriangle(直角三角形)的只读属性Hypotenuse(斜边)

  • 它有两个公有字段,表示直角三角形的两个直角边长度。这些字段可以被写入、读取
  • 第三边由属性Hypotenuse表示,是只读属性,其返回值基于另外两边长度
class RightTriangle
{public double A=3;public double B=4;public double Hypotenuse{get{return Math.Sqrt((A*A)+(B*B));}}
}
class Program
{static void Main(){var c=new RightTriangle();Console.WriteLine("Hypotenuse:{0}",c.Hypotenuse);}
}

自动实现属性

因为属性经常关联到后备字段,C#提供了自动实现属性(automatically implemented property),允许只声明属性而不声明后备字段。编译器为你创建隐藏的后备字段,并且字段挂接到get和set访问器上。 
自动属性的要点如下

  • 不声明后备字段-编译器根据属性类型分配存储
  • 不能提供访问器的方法体-它们必须被简单地声明为分号。get相当于简单的内存读,set相当于简单的写
  • 除非通过访问器,否则不能访问后备字段。因为不能用其他方法访问,所以实现只读和只写属性没有意义,因此使用自动属性必须同时提供读写访问器。

例:自动属性

class C1
{public int MyValue          //属性:分配内存{set;get;}
}
class Program
{static void Main(){C1 c=new C1();Console.WriteLine("MyValue:{0}",c.MyValue);c.MyValue=20;Console.WriteLine("MyValue:{0}",c.MyValue);}
}

除方便以外,自动属性使你在倾向于使用公有字段的地方很容易用属性将其替代。

静态属性

属性也可以声明为static。静态属性的访问器和静态成员一样,具有以下特点

  • 不能访问类的实例成员–它们能被实例成员访问
  • 不管类是否有实例,它们都存在
  • 当从类的外部访问时,必需使用类名引用

例:静态属性

class Trivial
{public static int MyValue{get;set;}public void PrintValue(){Console.WriteLine("Value from inside:{0}",MyValue);}
}
class Program
{static void Main(){Console.WriteLine("Init Value:{0}",Trival.MyValue);Trival.MyValue=10;Console.WriteLine("New Value:{0}",Trival.MyValue);var tr=new Trivial();tr.PrintValue();}
}

实例构造函数


实例构造函数是一个特殊的方法,它在创建类的每个新实例时执行。

  • 构造函数用于初始化类实例的状态
  • 如果希望从类的外部创建类的实例,需要将构造函数声明为public
class MyClass
{         和类名相同↓public MyClass(){     ↑没有返回类型...}
}
  • 构造函数的名称与类相同
  • 构造函数不能有返回值

例:使用构造函数初始化TimeOfInstantiation字段为当前时间

class MyClass
{DateTime TimeOfInstantiation;...public MyClass(){TimeOfInstantiation=DateTime.Now;}...
}

在学完静态属性后,我们可以仔细看看初始化TimeOfInstantiation那一行。DateTime类(实际上它是一个结构,但由于还没介绍结构,你可以先把它当成类)是从BCL中引入的,Now是类DateTime的静态属性。Now属性创建一个新的DateTime类实例,将其初始化为系统时钟中的当前日期和时间,并返回新DateTime实例的引用。

带参数的构造函数
  • 构造函数可以带参数。参数语法和其他方法完全相同
  • 构造函数可以被重载

例:有3个构造函数的Class

class Class1
{int Id;string Name;public Class1(){Id=28;Name="Nemo";}public Class1(int val){Id=val;Name="Nemo";}public Class1(String name){Name=name;}public void SoundOff(){Console.WriteLine("Name{0},Id{1}",Name,Id);}
}
class Program
{static void Main(){CLass1 a=new Class1(),b=new Class1(7),c=new Class1("Bill");a.SoundOff();b.SoundOff();c.SoundOff();}
}

默认构造函数

如果在类的声明中没有显式的提供实例构造函数,那么编译器会提供一个隐式的默认构造函数,它有以下特征。

  • 没有参数
  • 方法体为空

只要你声明了构造函数,编译器就不再提供默认构造函数。

例:显式声明了两个构造函数的Class2

class Class2
{public Class2(int Value){...}public Class2(string Value){...}
}
class Program
{static void Main(){Class2 a=new Class2();//错误!没有无参数的构造函数...}
}
  • 因为已经声明了构造函数,所以编译器不提供无参数的默认构造函数
  • 在Main中试图使用无参数的构造函数创建实例,编译器产生一条错误信息

静态构造函数


实例构造函数初始化类的每个新实例,static构造函数初始化类级别的项。通常,静态构造函数初始化类的静态字段。

  • 初始化类级别的项
    • 在引用任何静态成员之前
    • 在创建类的任何实例之前
  • 静态构造函数在以下方面与实例构造函数类似
    • 静态构造函数的名称和类名相同
    • 构造函数不能返回值
  • 静态构造函数在以下方面和实例构造函数不同
    • 静态构造函数声明中使用static
    • 类只能有一个静态构造函数,而且不能带参数
    • 静态构造函数不能有访问修饰符
class Class1
{static Class1{...}
}

关于静态构造函数还有其他要点

  • 类既可以有静态构造函数也可以有实例构造函数
  • 如同静态方法,静态构造函数不能访问类的实例成员,因此也不能是一个this访问器
  • 不能从程序中显式调用静态构造函数,系统会自动调用它们,在:
    • 类的任何实例被创建前
    • 类的任何静态成员被引用前

静态构造函数示例

class RandomNumberClass
{private static Random RandomKey;static RandomNumberClass(){RandomKey=new Random();}public int GetRandomNumber(){return RandomKey.Next();}
}
class Program
{static void Main(){var a=new RandomNumberClass();var b=new RandomNumberClass();Console.WriteLine("Next Random #:{0}",a.GetRandomNumber());Console.WriteLine("Next Random #:{0}",b.GetRandomNumber());}
}

对象初始化语句


对象初始化语句扩展了创建语法,允许你在创建新的对象实例时,设置字段和属性的值。


例:

new Point {X=5,Y=6};
  • 创建对象的代码必须能够访问初始化的字段和属性。如上例中,X和Y必须是public
  • 初始化发生在构造方法执行之后,因为构造方法中设置的值可能会在对象初始化中重置为不同的值
public class Point
{public int X=1;public int Y=2;
}
class Program
{static void Main(){var pt1=new Point();var pt2=new Point(X=5,Y=6);Console.WriteLine("pt1:{0},{1}",pt1.X,pt1.Y);Console.WriteLine("pt2:{0},{1}",pt2.X,pt2.Y);}
}

析构函数


析构函数(destructor)执行在类的实例被销毁前需要的清理或释放非托管资源行为。非托管资源通过Win32 API获得文件句柄,或非托管内存块。使用.NET资源无法得到它们,因此如果坚持使用.NET类,就无需为类编写析构函数。 
因此,我们等到第25章再描述析构函数。

readonly修饰符


字段可用readonly修饰。其作用类似于将字段声明为const,一旦值被设定就不能改变。

  • const字段只能在字段声明语句中初始化,而readonly字段可以在下列任意位置设置它的值
    • 字段声明语句,类似const
    • 类的任何构造函数。如果是static字段,初始化必须在静态构造函数中完成
  • const字段的值必须在编译时决定,而readonly字段值可以在运行时决定。这种增加的自由性允许你在不同环境或构造函数中设置不同的值
  • 和const不同,const的行为总是静态的,而readonly字段有以下两点
    • 它可以是实例字段,也可以是静态字段
    • 它在内存中有存储位置

例:Shape类,两个readonly字段

  • 字段PI在它的声明中初始化
  • 字段NumberOfSides根据调用的构造函数被设置为3或4
class Shape
{readonly double PI=3.1416;readonly int NumberOfSides;public Shape(double side1,double side2){// 矩形NumberOfSides=4;...}public Shape(double side1,double side2,double side3){// 三角形NumberOfSides=3;...}
}

this关键字


this关键字在类中使用,表示对当前实例的引用。它只能被用在下列类成员的代码块中。

  • 实例构造函数
  • 实例方法
  • 属性和索引器的实例访问器

静态成员不是实例的一部分,所以不能在静态函数成员中使用this。换句话说,this用于下列目的:

  • 用于区分类的成员和本地变量或参数
  • 作为调用方法的实参

例:MyClass类,在方法内使用this关键字区分两个Var1

class MyClass
{int Var1=10;public int ReturnMaxSum(int Var1){          参数  字段↓    ↓return Var1>this.Var1?Var1:this.Var1;}
}
class Program
{static void Main(){var mc=new MyClass();Console.WriteLine("Max:{0}",mc.ReturnMaxSum(30));Console.WriteLine("Max:{0}",mc.ReturnMaxSum(5));}
}

索引器


假如我们定义一个Employee类,它带有3个string型字段,如果不用索引器,我们用字段名访问它们。

class Employee
{public string LastName;public string FirstName;public string CityOfBirth;
}
class Program
{static void Main(){var emp1=new Employee();emp1.LaseName="Doe";emp1.FirstName="Jane";emp1.CityOfBirth="Dallas";}
}

如果能使用索引访问它们将会很方便,好像该实例是字段的数组一样。

    static void Main(){var emp1=new Employee();emp1[0]="Doe";emp1[1]="Jane";emp1[2]="Dallas";}

什么是索引器

索引器是一组get和set访问器,与属性类似。

索引器和属性

索引器和属性在很多方法类似

  • 和属性一样,索引器不用分配内存来存储
  • 索引器通常表示多个数据成员

可以认为索引器是为类的多个数据成员提供get、set属性。通过索引器,可以在许多可能的数据成员中进行选择。索引器本身可以是任何类型。

关于索引器的注意事项

  • 和属性一样,索引器可以只有一个访问器,也可以两个都有
  • 索引器总是实例成员,因此不能声明为static
  • 和属性一样,实现get、set访问器的代码不必一定关联到某字段或属性。这段代码可以什么都不做,只要get访问器返回某个指定类型值即可
声明索引器
  • 索引器没有名称。在名称的位置,关键词是this
  • 参数列表在方括号中
  • 参数列表中至少声明一个参数
Return Type this [Type param1,...]
{get{...}set{...}
}

声明索引器类似于声明属性。


索引器的set访问器

当索引器被用于赋值时,set访问器被调用,并接受两项数据

  • 一个隐式参数,名为value,value持有要保存的数据
  • 一个或多个索引参数,表示数据应该保存在哪里

下图例表明set访问器有如下语义

  • 它的返回类型为void
  • 它使用的参数列表和索引器声明中的相同
  • 它有一个名为value的隐式参数,值参类型和索引类型相同

索引器的get访问器

get访问器方法体内的代码必须检查索引参数,确定它表示哪个字段,并返回字段值。 
get访问器有如下语义

  • 它的参数列表和索引器声明中的相同
  • 它返回与索引器相同类型的值

关于索引器的补充

和属性一样,不能显示调用get、set访问器。取而代之,当索引器用在表达式中取值时,将自动调用get访问器。索引器被赋值时,自动调用set访问器。 
在“调用”索引器时,要在方括号中提供参数。

   索引   值↓    ↓
emp[0]="Doe";           //调用set访问器
string NewName=emp[0];  //调用get访问器
为Employee示例声明索引器

下面代码为示例中的类Employee声明了一个索引器

  • 索引器需要去写string类型的值,所以string必须声明为索引器的类型。它必须声明为public,以便从类外部访问
  • 3个字段被强行索引为整数0-2,所以本例中方括号中间名为index的形参必须为int型
  • 在set访问器方法体内,代码确定索引指的是哪个字段,并把隐式变量value赋给它。在get访问器方法体内,代码确定索引指的哪个字段,并返回该字段的值
class Employee
{public string LastName;public string FirstName;public string CityOfBirth;public string this[int index]{set{switch(index){case 0:LaseName=value;break;case 1:FirstName=value;break;case 2:CityOfBirth=value;break;default:throw new ArgumentOutOfRangeException("index");}}get{switch(index){case 0:return LaseName;case 1:return FirstName;case 2:return CityOfBirth;default:throw new ArgumentOutOfRangeException("index");}}}
}
另一个索引器示例

例:为类Class1的两个int字段设置索引

class Class1
{int Temp0;int Temp1;public int this[int index]{get{return(index==0?Temp0:Temp1;)}set{if(index==0){Temp0=value;}else{Temp1=value;}}}
}
class Example
{static void Main(){var a=new Class1();Console.WriteLine("Values -- T0:{0},T1:{1}",a[0],a[1]);a[0]=15;a[1]=20;Console.WriteLine("Values--T0:{0},T1:{1}",a[0],a[1]);}
}

索引器重载

类可以有任意多个参数列表不同的索引器。(返回类型不同,不是重载)

例:下面示例有3个索引器

class Myclass
{public string this[int index]{get{...}set{...}}public string this[int index1,int index2]{get{...}set{...}}public int this[float index1]{get{...}set{...}}
}

访问器的访问修饰符


本章中,你已看到了两种带get、set访问器的函数成员:属性和索引器。默认情况下,成员的两个访问器的访问级别和成员自身相同。也就是说,如果一个属性有public访问级别,那么它的两个访问器也是public的。

不过,你可以为两个访问器分配不同访问级别。例如,下面代码演示了一个常见且重要的例子–set访问器声明为private,get访问器声明为public。(get之所以是public,是因为属性的访问级别就是public)

注意:在这段代码中,尽管可以从类的外部读取该属性,但却只能在类的内部设置它。这是非常重要的封装工具。

class Person
{public string Name{get;private set;}public Person(string name){Name=name;}
}
class Program
{static public void Main(){var p=new Person("Capt,Ernest Evans");Console.WriteLine("Person's name is {0}",p.Name);}
}

访问器的访问修饰符有几个限制。最重要的限制如下。

  • 仅当成员(属性或索引器)既有get访问器也有set访问器时,其访问器才能有访问修饰符
  • 虽然两个访问器都必须出现,但它们中只能有一个有访问修饰符
  • 访问器的访问修饰符必须比成员的访问级别有更严格的限制性,即访问器的访问级别必须比成员的访问级别低,详见下图

例如,如果一个属性的访问级别是public,在图里较低的4个级别中,它的访问器可以使用任意一个。但如果属性的访问级别是protected,则其访问器唯一能使用的访问修饰符是private。

分部类和分部类型


类的声明可以分割成几个分部类的声明

  • 每个分部类的声明都含有一些类成员的声明
  • 类的分部类声明可以在同一文件中也可以在不同文件中

每个局部声明必须标为partial class,而不是class。分部类声明看起来和普通类声明相同。

类型修饰符partial不是关键字,所以在其他上下文中,可以把它用作标识符。但直接用在关键字class、struct或interface前时,它表示分部类型。

例:分部类


Visual Studio为标准的Windows程序模板使用了这个特性。当你从标准模板创建ASP.NET项目、Windows Forms项目或Windows Persentation Foudation(WPF)项目时,模板为每个Web页面、表单、WPF窗体创建两个类文件。

  • 一个文件的分部类包含由VS生成的代码,声明了页面上的组件。你不应该修改这个文件中的分部类,因为如果修改页面组件,VS会重新生成
  • 另一个文件包含的分部类可用于实现页面或表单组件的外观和行为
  • 除了分部类,还有另外两种分部类型
    • 局部结构(第10章)
    • 局部接口(第15章)

分部方法


分部方法是声明在分部类中不同部分的方法。 
分部方法的两个部分如下

  • 定义分部方法声明
    • 给出签名和返回类型
    • 声明的实现部分只是一个分号
  • 实现分部方法声明
    • 给出签名和返回类型
    • 是以正常形式的语句块实现

关于分部方法需要了解的重要内容如下

  • 定义声明和实现声明的签名和返回类型必须匹配。签名和返回类型有如下特征
    • 返回类型必须是void
    • 签名不能包括访问修饰符,这使分部方法是隐式私有的
    • 参数列表不能包含out参数
    • 在定义声明和实现声明中都必须包含上下文关键字partial,直接放在关键字void前
  • 可以有定义部分而没有实现部分。这种情况下,编译器把方法的声明以及方法内部任何对方法的调用都移除。不能只有实现部分而没有定义部分。

下面是一个名为PrintSum的分部方法的示例

  • 因为分部方法是隐式私有的,PrintSum不能从类的外部调用。方法Add是调用PrintSum的公有方法
partial class MyClass
{必须是void↓partial void PrintSum(int x,int y);//定义分部方法public void Add(int x,int y){PrintSum(x,y);}
}
partial class MyClass
{partial void PrintSum(int x,int y)//实现分部方法{Console.WriteLine("Sum i {0}",x+y);}
}
class Program
{static void Main(){var mc=new MyClass();mc.Add(5,6);}
}

 

from: http://www.cnblogs.com/moonache/p/6097402.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/276650.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Apache用户身份验证

原文链接:http://www.yylog.org/?p4830 Apache用户身份验证 在apache应用过程中,管理员经常需要对apache下的目录做一些限制,不希望所有用户都能访问该目录下的文件,只对指定用户访问,此时我们就要用到apache用户身…

c# 获取word表格中的内容_Java 获取、删除Word文本框中的表格

本文介绍如何来获取Word文本框中包含的表格,以及删除表格。程序测试环境包括:IDEAJDK 1.8.0Spire.Doc.jar注:jar导入,可通过创建Maven程序项目,并在pom.xml中配置Maven仓库路径,并指定Free Spire.Doc for J…

PS抠图方法[photoshop中文教程]

PS抠图方法 一、魔术棒法——最直观的方法   适用范围:图像和背景色色差明显,背景色单一,图像边界清晰。   方法意图:通过删除背景色来获取图像。   方法缺陷:对散乱的毛发没有用。   使用方法&#xff1a…

FastReport使用方法(C/S版)

前言 这两天群里一直有群友问一些关于FastReport的问题,结合他们的问题,在这里做一个整理,有不明白的可以加 FastReport 交流群 群 号:554714044 工具 VS2017 FastReport 开始 1.新建项目,添加三个按钮。预览、设计、…

如何设置Linux时区为东八区

当我们购买美国VPS或服务器的时候,默认情况下是美国时间。对于我们定时执行某些任务会带来麻烦,所以需要设置时区为东八区。登录SSH后,执行tzselect命令。我们这里选择亚洲5.这里选择china 9。一般选东八区(北京,广东&…

Windows 10系统安装JDK1.8与配置环境

第一步:下载JDK1.8 地址:https://www.oracle.com/index.html 第二步: 安装分两次,第一次是安装 jdk ,第二次是安装 jre 。安装jdk默认的安装地址为C盘,安装目录 \java 之前的目录修改成你想放的目录;安装jr…

git安装与配置_git 安装及基本配置

git 基本上来说是开发者必备工具了,在服务器里没有 git 实在不太能说得过去。何况,没有 git 的话,面向github编程 从何说起,如同一个程序员断了左膀右臂。你对流程熟悉后,只需要一分钟便可以操作完成原文地址: 服务器 …

Apache伪静态学习

原文链接:http://www.benben.cc/blog/?p305 Apache中有着这样一个模块,它默默无闻,却是URL操作的瑞士军刀!有人这样评价它:“尽管它的例子和文档数量可以以吨来计算,但它仍然是巫术,该死的巫术…

不同的容器里实现 RadioButton的单选

请教一个各位牛人一个问题,如图: (问题解决,见后面的解决方案~~) 怎么在不同的winform容器(GroupBox)里实现 RadioButton (如图中两个“详细照会”)的单选,请各位牛人给出实现的思…

html调用接口_搜狗ocr识别接口

详细情况在代码中说明,如果不想自己使用TensorFlow,可使用下面接口这是要识别的图片:最终识别的结果:This is a lot of 12 point text to test theocr code and see if it works on all typesof file format.The quick brown dog …

CSAcademy Or Problem

传送门 一口大锅( 斜率的确是有单调性 并且可以进行凸优化的 明明是证出来的 为什么自己就不相信呢( 我们发现对于当前点作为扩展的右端点 那么他前面至多有20个点会影响到这一段区间的或值 我们可以预处理记录出来这些节点的位置 很明显 答案随着右端点…

模块定义文件导出类_浓缩的就是精华——ES6模块精炼讲解

概述在 ES6 前, 实现模块化使用的是 RequireJS 或者 seaJS(分别是基于 AMD 规范的模块化库, 和基于 CMD 规范的模块化库)。ES6 引入了模块化,其设计思想是在编译时就能确定模块的依赖关系,以及输入和输出的变量。ES6 的模块化分为…

Jquery1.6版本后attr的变化

原文链接:http://www.cnblogs.com/-run/archive/2011/11/16/2251569.html Jquery1.6版本后attr的变化 Jquery1.6版本后 attr 改动后的效果: jquery1.6版本: 下文来自www.jquery.com The difference betweenattributes and properties can b…

idea main scanner 输入_哇晒,你竟然不知道idea的 Live Templates

最近公司新近来一名程序猿,在写代码时,美美写到System.out.println的时候,都要一母不差的用键盘敲上去,我问他你之前有用过eclipse中的快捷方法syso吗?于是,我给他介绍了一下,在idea中如何自定义…

偷梁换柱做自己的封装系统

偷梁换柱做自己的封装系统!菜鸟一开始都想把自己的信息加到系统里,但封装系统只会一点!但我们可“拿来”,我们可以用偷梁换柱的方法来修改别人的系统,本文以雨林的GHOST5.0系统为例。一、准备工作1、当然是下载一个自己…

台电u盘量产工具_简单几步,让U盘起死回生

如今,虽说云存储风靡,但U盘仍存在价值,毕竟在很多场合并不方便上网,即便如此网上存储有时也并不方便,也不安全。与此同时,如果是大文件存储,云盘上传和下载速度非常慢,并不适合海量数…

系统架构师 项目经理 哪个更有前景_中央空调加地暖与五恒系统,哪个更省钱?...

每逢严冬酷暑,人们都会感叹空调是最伟大的发明,并且随着科技发展还在不断进化。从烤火取暖到空调和地暖的供暖,从纸扇电扇的吹风到空调的制冷,人们的需求正在不断提高,于是,为了满足人们的需求,市场上又衍生出了家装五恒系统。 恒温、恒湿、恒氧、恒洁、恒静这…

c++直角坐标系与极坐标系的转换_一篇阅读量高达2百6十多万的关于坐标系和投影的相关知识探讨...

本文转载于CSDN作者rsyaoxin这是一篇关于坐标和投影的「神文」截止目前浏览量已达2698239是相关文章中不可打破的神话...文末有本文作者推荐的两款坐标转换的小工具下载链接回想一下,接触遥感专业也有几个年头了,而现在越来越偏离遥感了,突然…

query string parameters什么意思_public static void main(String[] args) 是什么意思?(转)...

public static void main(String[] args),是java程序的入口地址,java虚拟机运行程序的时候首先找的就是main方法。一、这里要对main函数讲解一下,参数String[] args是一个字符串数组,接收来自程度序执行时传进来的参数。如果是在控…

b样条曲面绘制 opengl_CAD制图软件中如何利用EXCEL输入坐标绘制曲线?

当在使用浩辰CAD制图软件绘制图纸的过程中,经常要绘制由多个坐标点连接成的曲线时,有什么方便快捷的方法吗?那当然是有的。利用EXCEL表格保存数据并与CAD制图软件巧妙地结合起来,就能很容易地画出曲线。下面给大家详细介绍一下吧&…