<<展现C#>> 第五章 类(修订)

Excel如何突出显示修订内容 通过本文的介绍,相信用户已经掌握了如何在Excel表格中突出显示修订内容的方法。这一功能对于团队协作和个人追踪修改历史非常有用,能够提高工作效率和协作效果。在设置和使用过程中,请注意上述注意事项和常见问题,确保功能正确实现并避免不必要的麻烦。在Excel表格中,修订内容的突出显示功能是一项非常实用的工具,通过简单的设置即可实现。希望本文能够帮助用户更好地利用这一功能,提高Excel的使用效率和体验。 阅读详情

 

第五章  类

      前一章讨论了数据类型和它们的用法。现在我们转移到C#中至关重要的结构——类。没有了类,就连简单的C#程序都不能编译。这一章假定你知道了一个类的基本组成部分:方法、属性、构造函数和析构函数。 C#在其中增加了索引和事件。
      在这一章中,你学到下列有关类的话题。
      。 使用构造函数和析构函数
      。给类写方法
      。给一个类增加属性存取标志
      。实现索引
      。创建事件并通过代表元为事件关联客户
      。应用类、成员和存取修饰符。

5.1  构造函数和析构函数
          在你可以访问一个类的方法、属性或任何其它东西之前, 第一条执行的语句是包含有相应类的构造函数。甚至你自己不写一个构造函数,也会有一个缺省的构造函数提供给你。

class TestClass
{
public TestClass(): base() {} // 由编译器提供
}

      一个构造函数总是和它的类名相同,但是,它没有声明返回类型。总之,构造函数总是public的,你可以用它们来初始化变量。

public TestClass()
{
// 在这给变量
// 初始化代码等等。
}

      如果类仅包含静态成员(能以类型调用,而不是以实例调用的成员),你可以创建一个private的构造函数。
private TestClass() {}
      尽管存取修饰符在这一章的后面将要大篇幅地讨论,但是private意味着从类的外面不可能访问该构造函数。所以,它不能被调用,且没有对象可以自该类定义被实例化。
      并不仅限于无参数构造函数——你可以传递初始参数来初始化成员。
      public TestClass(string strName, int nAge) { ... }

      作为一个C/C++程序员,你可能习惯于给初始化写一个附加的方法,因为在构造函数中没有返回值。当然,尽管在C#中也没有返回值,但你可以引发一个自制的异常,以从构造函数获得返回值。更多有关异常处理的知识在第七章 "异常处理"中有讨论。
      但是,当你保留引用给宝贵的资源,应该想到写一个方法来解决:一个可以被显式地调用来释放这些资源。问题是当你可以在析构函数(以类名的前面加"~"的方式命名)中做同样的事情时,为何还要写一个附加的方法.
public ~TestClass()
{
// 清除
}

    你应该写一个附加方法的原因是垃圾收集器,它在变量超出范围后并不会立即被调用,而仅当间歇期间或内存条件满足时才被触发。当你锁住资源的时间长于你所计划的时间时,它就会发生。因此,提供一个显式的释放方式是一个好主意,它同样能从析构函数中调用。

public void Release()
{
// 释放所有宝贵的资源
}

public ~TestClass()
{
Release();
}

    调用析构函数中的释放方法并不是必要的——总之,垃圾收集会留意释放对象。但没有忘记清除是一种良好的习惯。

5.2  方法
    既然对象能正确地初始化和结束,所剩下来的就是往类中增加功能。在大多数情况下,功能的主要部分在方法中能得到实现。你早已见过静态方法的使用,但是,这些是类型(类)的部分,不是实例(对象)。
    为了让你迅速入门,我把这些方法的烦琐问题安排为三节:
。方法参数
。改写方法
。方法屏蔽
5.2.1  方法参数
  因方法要处理更改数值,你多多少少要传递值给方法,并从方法获得返回值。以下三个部分涉及到由传递值和为调用者获取返回结果所引起的问题。

。输入参数
。引用参数
。输出参数

5.2.1.1  输入参数
  你早已在例子中见过的一个参数就是输入参数。你用一个输入参数通过值传递一个变量给一个方法——方法的变量被调用者传递进来的值的一个拷贝初始化。清单5.1 示范输入参数的使用。

清单  5.1 通过值传递参数

1: using System;
2:
3: public class SquareSample
4: {
5:  public int CalcSquare(int nSideLength)
6:  {
7:  return nSideLength*nSideLength;
8:  }
9: }
10:
11: class SquareApp
12: {
13:  public static void Main()
14:  {
15:  SquareSample sq = new SquareSample();
16:  Console.WriteLine(sq.CalcSquare(25).ToString());
17:  }
18: }

  因为我传递值而不是引用给一个变量,所以当调用方法时(见第16行),可以使用一个常量表达式(25)。整型结果被传回给调用者作为返回值,它没有存到中间变量就被立即显示到屏幕上 。
    输入参数按C/C++程序员早已习惯的工作方式工作。如果你来自VB,请注意没有能被编译器处理的隐式ByVal或ByRef——如果没有设定,参数总是用值传递。
    这点似乎与我前面所陈述的有冲突:对于一些变量类型,用值传递实际上意味着用引用传递。迷惑吗? 一点背景知识也不需要:COM中的东西就是接口,每一个类可以拥有一个或多个接口。一个接口只不过是一组函数指针,它不包含数据。重复该数组会浪费很多内存资源;所以,仅开始地址被拷贝给方法,它作为调用者,仍然指向接口的相同指针。那就是为什么对象用值传递一个引用。

5.2.1.2  引用参数
    尽管可以利用输入参数和返回值建立很多方法,但你一想到要传递值并原地修改它(也就是在相同的内存位置),就没有那么好运了。这里用引用参数就很方便。
void myMethod(ref int nInOut)
  因为你传递了一个变量给该方法(不仅仅是它的值),变量必须被初始化。否则,编译器会报警。清单 5.2 显示如何用一个引用参数建立一个方法。

清单 5.2  通过引用传递参数

1: // class SquareSample
2: using System;
3:
4: public class SquareSample
5: {
6:  public void CalcSquare(ref int nOne4All)
7:  {
8:  nOne4All *= nOne4All;
9:  }
10: }
11:
12: class SquareApp
13: {
14:  public static void Main()
15:  {
16:  SquareSample sq = new SquareSample();
17:
18:  int nSquaredRef = 20; // 一定要初始化
19:  sq.CalcSquare(ref nSquaredRef);
20:  Console.WriteLine(nSquaredRef.ToString());
21:  }
22: }

  正如所看到的,所有你要做的就是给定义和调用都加上ref限定符。因为变量通过引用传递,你可以用它来计算出结果并传回该结果。但是,在现实的应用程序中,我强烈建议要用两个变量,一个输入参数和一个引用参数。

5.2.1.3  输出参数
  传递参数的第三种选择就是把它设作一个输出参数。正如该名字所暗示,一个输出参数仅用于从方法传递回一个结果。它和引用参数的另一个区别在于:调用者不必先初始化变量才调用方法。这显示在清单5.3中。

清单  5.3  定义一个输出参数

1: using System;
2:
3: public class SquareSample
4: {
5:  public void CalcSquare(int nSideLength, out int nSquared)
6:  {
7:  nSquared = nSideLength * nSideLength;
8:  }
9: }
10:
11: class SquareApp
12: {
13:  public static void Main()
14:  {
15:  SquareSample sq = new SquareSample();
16: 
17:  int nSquared; // 不必初始化
18:  sq.CalcSquare(15, out nSquared);
19:  Console.WriteLine(nSquared.ToString());
20:  }
21: }


5.2.2  改写方法
    面向对象设计的重要原则就是多态性。不要理会高深的理论,多态性意味着:当基类程序员已设计好用于改写的方法时,在派生类中,你就可以重定义(改写)基类的方法。基类程序员可以用 virtual 关键字设计方法:
virtual void CanBOverridden()
    当从基类派生时,所有你要做的就是在新方法中加入override关键字:
override void CanBOverridden()
    当改写一个基类的方法时,你必须明白,不能改变方法的访问属性——在这章的后面,你会学到更多关于访问修饰符的知识。
    除了改写基类方法的事实外,还有另一个甚至更重要的改写特性。当把派生类强制转换成基类类型并接着调用虚拟方法时,被调用的是派生类的方法而不是基类的方法。
((BaseClass)DerivedClassInstance).CanBOverridden();
      为了演示虚拟方法的概念,清单 5.4 显示如何创建一个三角形基类,它拥有一个可以被改写的成员方法(ComputeArea)。

清单 5.4  改写一个基类的方法

1: using System;
2:
3: class Triangle
4: {
5:  public virtual double ComputeArea(int a, int b, int c)
6:  {
7:  // Heronian formula
8:  double s = (a + b + c) / 2.0;
9:  double dArea = Math.Sqrt(s*(s-a)*(s-b)*(s-c));
10:  return dArea;
11:  }
12: }
13:
14: class RightAngledTriangle:Triangle
15: {
16:  public override double ComputeArea(int a, int b, int c)
17:  {
18:  double dArea = a*b/2.0;
19:  return dArea;
20:  }
21: }
22:
23: class TriangleTestApp
24: {
25:  public static void Main()
26:  {
27:  Triangle tri = new Triangle();
28:  Console.WriteLine(tri.ComputeArea(2, 5, 6));
29: 
30:  RightAngledTriangle rat = new RightAngledTriangle();
31:  Console.WriteLine(rat.ComputeArea(3, 4, 5));
32:  }
33: }

    基类Triangle定义了方法ComputeArea。它采用三个参数,返回一个double结果,且具有公共访问性。从Triangle类派生出的是RightAngledTriangle,它改写了ComputeArea 方法,并实现了自己的面积计算公式。两个类都被实例化,且在命名为TriangleTestApp的应用类的Main() 方法中得到验证。
我漏了解释第14行:
class RightAngledTriangle : Triangle
    在类语句中冒号(:)表示RightAngledTriangle从类 Triangle派生。那就是你所必须要做的,以让C#知道你想把 Triangle当作RightAngledTriangle的基类。
    当仔细观察直角三角形的ComputeArea方法时,你会发现第3个参数并没有用于计算。但是,利用该参数就可以验证是否是“直角”。如清单5.5所示。

清单 5.5  调用基类实现

1: class RightAngledTriangle:Triangle
2: {
3:  public override double ComputeArea(int a, int b, int c)
4:  {
5:  const double dEpsilon = 0.0001;
6:  double dArea = 0;
7:  if (Math.Abs((a*a + b*b - c*c)) > dEpsilon)
8:  {
9:    dArea = base.ComputeArea(a,b,c);
10:  }
11:  else
12:  {
13:    dArea = a*b/2.0;
14:  }
15:
16:  return dArea;
17:  }
18: }

  该检测简单地利用了毕达哥拉斯公式,对于直角三角形,检测结果必须为0。如果结果不为0,类就调用它基类的 ComputeArea来实现。
dArea = base.ComputeArea(a,b,c);
  例子的要点为:通过显式地利用基类的资格检查,你就能轻而易举地调用基类实现改写方法。
当你需要实现其在基类中的功能,而不愿意在改写方法中重复它时,这就非常有帮助。

5.2.3 方法屏蔽
    重定义方法的一个不同手段就是要屏蔽基类的方法。当从别人提供的类派生类时,这个功能特别有价值。看清单 5.6,假设BaseClass由其他人所写,而你从它派生出 DerivedClass 。

清单 5.6  Derived Class 实现一个没有包含于 Base Class中的方法

1: using System;
2:
3: class BaseClass
4: {
5: }
6:
7: class DerivedClass:BaseClass
8: {
9:  public void TestMethod()
10:  {
11:  Console.WriteLine("DerivedClass::TestMethod");
12:  }
13: }
14:
15: class TestApp
16: {
17:  public static void Main()
18:  {
19:  DerivedClass test = new DerivedClass();
20:  test.TestMethod();
21:  }
22: }

    在这个例子中, DerivedClass 通过TestMethod()实现了一个额外的功能。但是,如果基类的开发者认为把TestMethod()放在基类中是个好主意,并使用相同的名字实现它时,会出现什么问题呢?(见清单5.7)

清单 5.7    Base Class 实现和 Derived Class相同的方法

1: class BaseClass
2: {
3:  public void TestMethod()
4:  {
5:  Console.WriteLine("BaseClass::TestMethod");
6:  }
7: }
8:
9: class DerivedClass:BaseClass
10: {
11:  public void TestMethod()
12:  {
13:  Console.WriteLine("DerivedClass::TestMethod");
14:  }
15: }

在优秀的编程语言中,你现在会遇到一个真正的大麻烦。但是,C#会给你提出警告:
hiding2.cs(13,14): warning CS0114: 'DerivedClass.TestMethod()' hides inherited member 'BaseClass.TestMethod()'. To make the current method override that implementation, add the override keyword. Otherwise add the new keyword.
(hiding2.cs(13,14):警告  CS0114:'DerivedClass.TestMethod()' 屏蔽了所继承的成员'BaseClass.TestMethod()'。要想使当前方法改写原来的实现,加上 override关键字。否则加上新的关键字。)
具有了修饰符new,你就可以告诉编译器,不必重写派生类或改变使用到派生类的代码,你的方法就能屏蔽新加入的基类方法。清单5.8  显示如何在例子中运用new修饰符。

清单  5.8  屏蔽基类方法

1: class BaseClass
2: {
3:  public void TestMethod()
4:  {
5:  Console.WriteLine("BaseClass::TestMethod");
6:  }
7: }
8:
9: class DerivedClass:BaseClass
10: {
11:  new public void TestMethod()
12:  {
13:  Console.WriteLine("DerivedClass::TestMethod");
14:  }
15: }

使用了附加的new修饰符,编译器就知道你重定义了基类的方法,它应该屏蔽基类方法。但是,如果你按以下方式编写:
DerivedClass test = new DerivedClass();
((BaseClass)test).TestMethod();
  基类方法的实现就被调用了。这种行为不同于改写方法,后者保证大部分派生方法获得调用。

5.3  类属性
    有两种途径揭示类的命名属性——通过域成员或者通过属性。前者是作为具有公共访问性的成员变量而被实现的;后者并不直接回应存储位置,只是通过 存取标志(accessors)被访问。
    当你想读出或写入属性的值时,存取标志限定了被实现的语句。用于读出属性的值的存取标志记为关键字get,而要修改属性的值的读写符标志记为set。
在你对该理论一知半解以前,请看一下清单5.9中的例子,属性SquareFeet被标上了get和set的存取标志。
清单 5.9  实现属性存取标志

1: using System;
2:
3: public class House
4: {
5:  private int m_nSqFeet;
6:
7:  public int SquareFeet
8:  {
9:  get { return m_nSqFeet; }
10:  set { m_nSqFeet = value; }
11:  }
12: }
13:
14: class TestApp
15: {
16:  public static void Main()
17:  {
18:  House myHouse = new House();
19:  myHouse.SquareFeet = 250;
20:  Console.WriteLine(myHouse.SquareFeet);
21:  }
22: }

    House类有一个命名为SquareFeet的属性,它可以被读和写。实际的值存储在一个可以从类内部访问的变量中——如果你想当作一个域成员重写它,你所要做的就是忽略存取标志而把变量重新定义为:
public int SquareFeet;
对于一个如此简单的变量,这样不错。但是,如果你想要隐藏类内部存储结构的细节时,就应该采用存取标志。在这种情况下,set 存取标志给值参数中的属性传递新值。(可以改名,见第10行。)
除了能够隐藏实现细节外,你还可自由地限定各种操作:
get和set:允许对属性进行读写访问。
get only:只允许读属性的值。
set only:只允许写属性的值。
除此之外,你可以获得实现在set标志中有效代码的机会。例如,由于种种原因(或根本没有原因),你就能够拒绝一个新值。最好是没有人告诉你它是一个动态属性——当你第一次请求它后,它会保存下来,故要尽可能地推迟资源分配。

5.4  索引
  你想过象访问数组那样使用索引访问类吗 ?使用C#的索引功能,对它的期待便可了结。

语法基本上象这样:
属性  修饰符  声明 { 声明内容}

具体的例子为
public string this[int nIndex]
{
get { ... }
set { ... }
}

索引返回或按给出的index设置字符串。它没有属性,但使用了public修饰符。声明部分由类型string和this 组成用于表示类的索引。
get和set的执行规则和属性的规则相同。(你不能取消其中一个。) 只存在一个差别,那就是:你几乎可以任意定义大括弧中的参数。限制为,必须至少规定一个参数,允许ref  和out  修饰符。
this关键字确保一个解释。索引没有用户定义的名字,this 表示默认接口的索引。如果类实现了多个接口,你可以增加更多个由InterfaceName.this说明的索引。

  为了演示一个索引的使用,我创建了一个小型的类,它能够解析一个主机名为IP地址——或一个IP地址列表(以http://www.microsoft.com为例 )。这个列表通过索引可以访问,你可以看一下清单5.10 的具体实现。

清单  5.10  通过一个索引获取一个IP地址

1: using System;
2: using System.Net;
3:
4: class ResolveDNS
5: {
6:  IPAddress[] m_arrIPs;
7:
8:  public void Resolve(string strHost)
9:  {
10:  IPHostEntry iphe = DNS.GetHostByName(strHost);
11:  m_arrIPs = iphe.AddressList;
12:  }
13:
14:  public IPAddress this[int nIndex]
15:  {
16:  get
17:  {
18:    return m_arrIPs[nIndex];
19:  }
20:  }
21:
22:  public int Count
23:  {
24:  get { return m_arrIPs.Length; }
25:  }
26: }
27:
28: class DNSResolverApp
29: {
30:  public static void Main()
31:  {
32:  ResolveDNS myDNSResolver = new ResolveDNS();
33:  myDNSResolver.Resolve("http://www.microsoft.com");
34:
35:  int nCount = myDNSResolver.Count;
36:  Console.WriteLine("Found {0} IP's for hostname", nCount);
37:  for (int i=0; i < nCount; i++)
38:    Console.WriteLine(myDNSResolver[i]);
39:  } 
40: }

    为了解析主机名,我用到了DNS类,它是System .Net 名字空间的一部分。但是,由于这个名字空间并不包含在核心库中,所以必须在编译命令行中引用该库:
csc /r:System.Net.dll /out:resolver.exe dnsresolve.cs
    解析代码是向前解析的。在该  Resolve方法中,代码调用DNS类的静态方法GetHostByName,它返回一个IPHostEntry对象。结果,该对象包含有我要找的数组——AddressList数组。在退出Resolve 方法之前,在局部的对象实例成员m_arrIPs中,存储了一个AddressList array的拷贝(类型IPAddress 的对象存储在其中)。
    具有现在生成的数组 ,通过使用在类ResolveDNS中求得的索引,应用程序代码就可以在第37至38行列举出IP地址。(在第6章 "控制语句",有更多有关语句的信息。)  因为没有办法更改IP地址,所以仅给索引使用了get存取标志。为了简单其见,我忽略了数组的边界溢出检查。
 
5.4  事件
  当你写一个类时,有时有必要让类的客户知道一些已经发生的事件。如果你是一个具有多年编程经验的程序员,似乎有很多的解决办法,包括用于回调的函数指针和用于ActiveX控件的事件接收(event sinks)。现在你将要学到另外一种把客户代码关联到类通知的办法——使用事件。
    事件既可以被声明为类域成员(成员变量),也可以被声明为属性。两者的共性为,事件的类型必定是代表元,而函数指针原形和C#的代表元具有相同的含义。
    每一个事件都可以被0或更多的客户占用,且客户可以随时关联或取消事件。你可以以静态或者以实例方法定义代表元,而后者很受C++程序员的欢迎。
    既然我已经提到了事件的所有功能及相应的代表元,请看清单5.11中的例子。它生动地体现了该理论。

清单5.11  在类中实现事件处理
1: using System;
2:
3: // 向前声明
4: public delegate void EventHandler(string strText);
5:
6: class EventSource
7: {
8:  public event EventHandler TextOut;
9:
10:  public void TriggerEvent()
11:  {
12:  if (null != TextOut) TextOut("Event triggered");
13:  }
14: }
15:
16: class TestApp
17: {
18:  public static void Main()
19:  {
20:  EventSource evsrc = new EventSource();
21: 
22:  evsrc.TextOut += new EventHandler(CatchEvent);
23:  evsrc.TriggerEvent();
24:
25:  evsrc.TextOut -= new EventHandler(CatchEvent);
26:  evsrc.TriggerEvent();
27:
28:  TestApp theApp = new TestApp();
29:  evsrc.TextOut += new EventHandler(theApp.InstanceCatch);
30:  evsrc.TriggerEvent();
31:  }
32:
33:  public static void CatchEvent(string strText)
34:  {
35:  Console.WriteLine(strText);
36:  }
37:
38:  public void InstanceCatch(string strText)
39:  {
40:  Console.WriteLine("Instance " + strText);
41:  }
42: }

    第4行声明了代表元(事件方法原形),它用来给第8行中的EventSource类声明TextOut事件域成员。你可以观察到代表元作为一种新的类型声明,当声明事件时可以使用代表元。
    该类仅有一个方法,它允许我们触发事件。请注意,你必须进行事件域成员不为null的检测,因为可能会出现没有客户对事件感兴趣这种情况。
    TestApp类包含了Main 方法,也包含了另外两个方法,它们都具备事件所必需的信号。其中一个方法是静态的,而另一个是实例方法。
    EventSource 被实例化,而静态方法CatchEvent被预关联上了 TextOut事件:
evsrc.TextOut += new EventHandler(CatchEvent);
    从现在起,当事件被触发时,该方法被调用。如果你对事件不再感兴趣,简单地取消关联:
evsrc.TextOut -= new EventHandler(CatchEvent);
    注意,你不能随意取消关联的处理函数——在类代码中仅创建了这些处理函数。为了证明事件处理函数也和实例方法一起工作,余下的代码建立了TestApp 的实例,并钩住事件处理方法。
    事件在哪方面对你特别有用?你将经常在ASP+中或使用到WFC (Windows Foundation Classes)时,涉及到事件和代表元。

5.5  应用修饰符
    在这一章的学习过程中,你已经见过了象public、virtual等修饰符。欲以一种易于理解的方法概括它们,我把它们划分为三节:

。类修饰符
。成员修饰符
。存取修饰符

5.5.1 类修饰符
    到目前为止,我还没有涉及到类修饰符,而只涉及到了应用于类的存取修饰符。但是,有两个修饰符你可以用于类:
    abstract——关于抽象类的重要一点就是它不能被实例化。只有不是抽象的派生类才能被实例化。派生类必须实现抽象基类的所有抽象成员。你不能给抽象类使用sealed 修饰符。
    sealed——密封 类不能被继承。使用该修饰符防止意外的继承,在.NET框架中的类用到这个修饰符。
    要见到两个修饰符的运用,看看清单5.12 ,它创建了一个基于一个抽象类的密封类(肯定是一个十分极端的例子)。

清单  5.12  抽象类和密封类

1: using System;
2:
3: abstract class AbstractClass
4: {
5:  abstract public void MyMethod();
6: }
7:
8: sealed class DerivedClass:AbstractClass
9: {
10:  public override void MyMethod()
11:  {
12:  Console.WriteLine("sealed class");
13:  }
14: }
15:
16: public class TestApp
17: {
18:  public static void Main()
19:  {
20:  DerivedClass dc = new DerivedClass();
21:  dc.MyMethod();
22:  }
23: }

5.5.2  成员修饰符
    与有用的成员修饰符的数量相比,类修饰符的数量很少。我已经提到了一些,这本书即将出现的例子描述了其它的成员修饰符。
    以下是有用的成员修饰符:
        abstract——说明一个方法或存取标志不能含有一个实现。它们都是隐式虚拟,且在继承类中,你必须提供 override关键字。
    const——这个修饰符应用于域成员或局部变量。在编译时常量表达式被求值,所以,它不能包含变量的引用。
    event ——定义一个域成员或属性作为类型事件。用于捆绑客户代码到类的事件。
    extern——告诉编译器方法实际上由外部实现。第10章 “和非受管代码互相操作” 将全面地涉及到外部代码。
    override——用于改写任何基类中被定义为virtual的方法和存取标志。要改写的名字和基类的方法必须一致。
    readonly——一个使用  readonly修饰符的域成员只能在它的声明或者在包含它的类的构造函数中被更改。

    static——被声明为static的成员属于类,而不属于类的实例。你可以用static 于域成员、方法、属性、操作符甚至构造函数。
    virtual——说明方法或存取标志可以被继承类改写。

5.5.3  存取修饰符
    存取修饰符定义了某些代码对类成员(如方法和属性)的存取等级。你必须给每个成员加上所希望的存取修饰符,否则,默认的存取类型是隐含的。
    你可以应用4个 存取修饰符之一:
    public——任何地方都可以访问该成员,这是具有最少限制的存取修饰符。
    protected——在类及所有的派生类中可以访问该成员,不允许外部访问。
    private——仅仅在同一个类的内部才能访问该成员。甚至派生类都不能访问它。
    internal——允许相同组件(应用程序或库)的所有代码访问。在.NET组件级别,你可以把它视为public,而在外部则为private。
    为了演示存取修饰符的用法,我稍微修改了Triangle例子,使它包含了新增的域成员和一个新的派生类(见清单 5.13)。

清单 5.13  在类中使用存取修饰符

1: using System;
2:
3: internal class Triangle
4: {
5:  protected int m_a, m_b, m_c;
6:  public Triangle(int a, int b, int c)
7:  {
8:  m_a = a;
9:  m_b = b;
10:  m_c = c;
11:  }
12:
13:  public virtual double Area()
14:  {
15:  // Heronian formula
16:  double s = (m_a + m_b + m_c) / 2.0;
17:  double dArea = Math.Sqrt(s*(s-m_a)*(s-m_b)*(s-m_c));
18:  return dArea;
19:  }
20: }
21:
22: internal class Prism:Triangle
23: {
24:  private int m_h;
25:  public Prism(int a, int b, int c, int h):base(a,b,c)
26:  {
27:  m_h = h;
28:  }
29:
30:  public override double Area()
31:  {
32:  double dArea = base.Area() * 2.0;
33:  dArea += m_a*m_h + m_b*m_h + m_c*m_h;
34:  return dArea;
35:  }
36: }
37:
38: class PrismApp
39: {
40:  public static void Main()
41:  {
42:  Prism prism = new Prism(2,5,6,1);
43:  Console.WriteLine(prism.Area());
44:  }
45: }

        Triangle 类和 Prism 类现在被标为 internal。这意味着它们只能在当前组件中被访问。请记住“.NET组件”这个术语指的是包装( packaging,),而不是你可能在COM+中用到的组件。Triangle 类有三个 protected成员,它们在构造函数中被初始化,并用于面积计算的方法中。由于这些成员是protected 成员,所以我可以在派生类Prism中访问它们,在那里执行不同的面积计算。Prism自己新增了一个成员m_h,它是私有的——甚至派生类也不能访问它。
      花些时间为每个类成员甚至每个类计划一种保护层次,通常是个好主意。当需要引入修改时,全面的计划最终会帮助你,因为没有程序员会愿意使用“没有文档”的类功能。
5.6  小结
    这章显示了类的各种要素,它是运行实例(对象)的模板。在一个对象的生命期,首先被执行的代码是个构造函数。构造函数用来初始化变量,这些变量后来在方法中用于计算结果。
    方法允许你传递值、引用给变量,或者只传送一个输出值。方法可以被改写以实现新的功能,或者你可以屏蔽基类成员,如果它实现了一个具有和派生类成员相同名字的方法。
    命名属性可以被当作域成员(成员变量)或属性存取标志实现。后者是get和set存取标志,忽略一个或另外一个,你可以创建仅写或仅读属性。存取标志非常适合于确认赋给属性的值。
    C#类的另外一个功能是索引,它使象数组语法一样访问类中值成为可能。还有,如果当类中的某些事情发生时,你想客户得到通知,要让它们与事件关联。
    当垃圾收集器调用析构函数时,对象的生命就结束了。由于你不能准确地预测这种情况什么时候会发生,所以应该创建一个方法以释放这些宝贵的资源,当你停止使用它们时。
哈工大2024春大数据挖掘期末考试回忆版试题 (3)现有一个用户数量远多于物品(还是物品远多于用户,忘了是哪个了)的情景,应该使用基于user-user的推荐系统还是item-itmen的推荐系统,说明原因。(1)L是n*n拉普拉斯矩阵,x是一个n*1列向量,证明对任意的x,(x)^TLx>=0(6分)(3)证明:p到o的K-可达距离,至少是o的K-distance(2分)(2)(1)的结果说明竞争率的上界是多少?(2)考Gain的意义,如何利用Gain选择划分点(2分)(2)说明如何利用划分后的数据进行验证(2分)(2)k可达距离是对称的吗? 阅读详情

相关推荐

告别枯燥的Unity编辑器脚本:Odin插件如何让游戏数据配置变得像搭积木一样简单?

本文详细介绍了Odin插件如何简化Unity编辑器脚本开发,使游戏数据配置变得直观高效。通过声明式特性和丰富的可视化工具,Odin让开发者告别繁琐的OnGUI代码,实现所见即所得的开发体验。文章重点展示了Odin的核心优势、魔法特性及实战案例,帮助Unity开发者提升工作效率。

weixin_28717411的博客 345

c#对象池详解

在系统设计中,经常会使用“池”的概念。比如数据库连接池,socket连接池,线程池,组件队列。“池”可以节省对象重复创建和初始化所耗费 的时间,可以简化对象获取和使用的过程。对于那些被系统频繁请求和使用的对象,如果使用这种机制,可以使系统性能得到很大提高。特别象数据库连接这种对 象,客户端与数据库服务器端建立连接时,是比较慢的,如果每次进行数据库操作,都要先进行数据库连接,系统效率将非常低下。...

lfanyize的博客 4189

Veristand simulink 集成 simulink 非正版如何处理

1、安装 veristand model Generation Support add-on。matlab 安装veristand model support 记录。下载 以后 如果非正版matlab 直接拖命令行执行。下载后直接命令行执行即可。

flik9527的博客 598

<<展现C#>> 第八章 用C#写组件(修订)

第八章    用C#写组件 这一章关于用C#写组件。你学到如何写一个组件,如何编译它,且如何在一个客户程序中使用它。更深入一步是运用名字空间来组织你的应用程序。这章由两个主要大节构成:。你的第一个组件。使用名字空间工作 8.1  你的第一个组件 到目前为止,在本书中提到的例子都是在同一个应用程序中直接使用一个类。类和它的使用者被包含在同一个执行文件中。现在我们

rainbow的专栏 1632

NotImplementedError: Cannot copy out of meta tensor; no data! Please use torch.nn.Module.to_empty()

Please use torch.nn.Module.to_empty() instead of torch.nn.Module.to()输出(llama) (base) [root@localhost home]# nvidia-smi Unable to determine the device handle for GPU0000:09:00.0: Unknown Error。这是典型的“孤儿进程”,但它还是。(僵尸进程),占用 GPU 资源。如果进程仍然不消失,并且。,这意味着它的父进程是。

m0_61636632的博客 2096

别再手动更新Excel了!用Jira看板管理你的个人项目(附保姆级配置流程)

本文详细介绍了如何利用Jira看板替代Excel进行个人项目管理,提供从注册到配置的保姆级教程。通过极简看板模式、自动化规则和数据分析功能,帮助用户实现任务可视化、协作高效化和提醒自动化,大幅提升个人工作效率。特别适合需要管理复杂个人项目的用户。

weixin_42699679的博客 229

<<展现C#>>第四章 C#类型(修订)

 第四章        C#类型     既然你已经知道如何创建一个简单的C#程序,那么我将会给你介绍C#的类型系统。在这一章中,你学到如何使用不同的值和引用类型,加框和消框机制能为你作些什么。尽管这一章的并不侧重于例子,但你还可以学到很多重要的、关于如何使用现成类型创建程序的知识。4.1 值类型 各种值类型总是含有相应该类型的一个值。C#迫使你初始化变量才能使用它们进行

rainbow的专栏 1585

【GAMES101】现代计算机图形学入门 资料整理

课程网址 课程主页 包括ppt 课程PPT和视频 一篇搞定!GAMES101现代计算机图形学入门(全) 作业链接地址 作业论坛 GAMES101课程环境搭建(Win10+VSCode) 计算机图形学笔记 虎书pdf 提取:n2rx 【必须】:高等数学(微积分、线性代数)、算法、数据结构 【非必须】:信号处理、数值分析、大学物理(光学) 在线公式编辑器 一定要做作业,如果个人要配置环境的话应该是配置opencv,eigen这两个核心库就行了,目前觉得最核心的一个作业是作业3,实现着色器和渲染管线,做完一遍

ysh的博客 2684

<<展现C#>>第六章 控制语句(修订)

                                  第六章   控制语句      有一种语句,你在每种编程语言控制流程语句中都可以找到。在这一章中,我介绍C#的控制语句,它们分为两个主要部分:。选择语句。循环语句如果你是C或C++程序员,很多信息会让你感到似曾相似;但是,你必须知道它们还存在着一些差异。  6.1 选择语句    当运

rainbow的专栏 1559

STM32的GPS数据提取程序设计说明——基于NMEA0183协议

一、硬软件平台 本次程序实现效果为对GPS信号穿送来的数据进行筛选,并将筛选后的信息通过上位机显示出来,所以此次设计所需硬件包括STM32F407、RS232转TTL、CH340USB转串口模块,注意该模块在使用前,对应的系统需要安装驱动,否则串口调试助手无法识别,另外还包括JLink下载器。 本次代码设计软件为KEIL5并结合F4固件包,上位机系统为WIN7,主机系统为WIN10。 二、算法总体思路设计 由于GPS通过RS232将数据传送给板子,因此使用两个串口资源(串口1和串口2),其中一个用来

小景的博客 6181

GRCNN抓取网络学习3【自制Jacquard数据集模型调优】

请先完成,这样有利于下面操作的理解!然后第二步是,有了自己的数据集输入到GRCNN中是三种文件,那么可以GRCNN抓取网络学习2【自制Jacquard数据集训练】!既然学会训练,那么开展下一步计划模型调优。GRCNN源码:https://github.com/skumra/robotic-grasping。

vor234的博客 2502

<<展现C#>> 第七章 异常处理(修订)

     第七章   异常处理     通用语言运行时(CLR)具有的一个很大的优势为异常处理是跨语言被标准化的。一个在C#中所引发的异常可以在Visual Basic客户中得到处理。不再有 HRESULTs  或者 ISupportErrorInfo 接口。    尽管跨语言异常处理的覆盖面很广,但这一章完全集中讨论C#异常处理。你稍为改变编译器的溢出处理行为,接着有趣的事情就

rainbow的专栏 1417

Windows 下 Claude Code Desktop 安装与使用全记录:从零到一的保姆级图文教程

本文主要介绍了Windows 下 Claude Code 安装全记录:从零到一的保姆级图文教程,希望能对学习使用 Claude Code 的同学们有所帮助。 文章目录 1. 背景 2. 安装Claude Code Desktop 3. 配置第三方API 4. 日常使用

weixin_43178406的博客 8万+

<<展现C#>> 第二章 NGWS Runtime 技术基础(修订)

                   第二章  NGWS  runtime 技术基础     既然你已经具有了C#全面的印象,我也想让你领略NGWS runtime的全貌。C#依靠由NGWS提供的运行时;因此,有必要知道运行时如何工作,以及它背后所蕴含的概念。    所以,这一章分为两部分——它们是所有的概念和应用的基础。两部分的内容虽然有些重叠,但有助于加深理解正在学习的

rainbow的专栏 2131

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

<<展现C#>> 前言(补充) 和第三章 第一个C#程序(修订)

 前言0.1  提要    欢迎阅读《展现 C#》(Presenting C#)。这本书是你提高企业编程语言的一条捷径。这种企业编程语言带有下一代编程语言服务运行时(NGWS Runtime):C#(发音"C sharp")。    NGWS Runtime 是一个不仅管理执行代码、同时也提供使编程更加容易的动态环境。编译器产生受管代码以指向这种受管理执行环境。你获得跨平台语言集成、跨平台语言

rainbow的专栏 1465

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

[翻译] 在 Overleaf 中追踪修订

在你的 LaTeX 代码中追踪修订和添加评论是非常有用的功能。你可以追踪其他合作者所作出的改动,使用评论功能去对改动作出建议,向合作者们获取资源,讨论项目的细节,或者简单地说明还需要完成的事项等等。评论还可以被回复,被解决,或者永久地被删除。这篇文章将会介绍如何使用这个在 Overleaf v2 中新加入的功能。

Xovee 4万+

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

WPS修订模式

WPS修订模式是专为多人协作设计的文档编辑功能,能追踪所有修改痕迹并区分不同作者的修改内容。通过红色删除线和下划线标注增删内容,并记录修改者、时间等信息。审核者可选择性接受或拒绝修改,支持批量操作。该模式保留完整修改历史便于追溯,可与批注功能结合实现修改沟通。操作步骤包括开启修订模式、自定义修订风格、更改用户名、设置修订状态、筛选审阅信息等,最终可逐个或批量确认修订内容。修订模式有效解决了多人协作中的版本混乱问题,提升文档审核效率。

深耕信创・网络・云原生领域 3220

word接受修订灰色_Word高频问题之一:修订模式

1小剧场仅用于了解使用情境StartnowCindy,根据今天会议上大家讨论的结果,回去把这些资料都对应修改下。好的,老大。One daylater老大,资料我都已经全部改好了,请过目。不错不错,效率挺快的嘛,我看下。(面色晴转多云)所以你究竟都改了哪些地方?嗯,我改了挺多地方的,所以,具体都有哪里我也记不清楚了。你为什么不作标记呢?(脸上笑眯眯,心里NND:这是想让我从头重新再...

weixin_35012535的博客 9808

word一键接受所有修订并保留修订痕迹

如果插入的内容没有全部变蓝,把word文档划到第一页,再次执行。

qq_45538220的博客 7502

word修订模式怎么彻底关闭_word修订模式取消不了

本文收集整理关于word修订模式取消不了的相关议题,使用内容导航快速到达。内容导航:Q1:word修订模式怎么取消你好,修订编辑模式可以防止用户误操作。我们可以根据需要取消修订编辑模式,方法如下:打开Word2010文档窗口,切换到“审阅”功能区。在“修订”分组中单击“修订”按钮使其显示为非启用状态即可,单击“修订”按钮取消修订编辑模式。Q2:WORD怎么取消修订模式以word2007取消修订模式...

weixin_39928993的博客 2万+

word修订模式怎么彻底关闭_如何去掉word修订模式

如何去除word文档中的修订模式工具/原料:word2007软件。1.打开word文档,单击“审阅”菜单,单击“修订”命令启用修订功能,如下图:2.对文档进行的修改都会被标记,如下图:3.单击“修订”命令即可关闭修订功能,如下图:4.单击“显示以供审阅”下拉按钮,如下图,可以设置修订标记的显示状态。5.在“显示以供审阅”下拉列表(如下图)中选择下列操作:(1)单击“显示标记的最终状态”命令:在文档...

weixin_39596720的博客 2万+

jacob去除word修订

jacob去除word里面的修订。 因为本人是做去除表格操作的时候因为当前word文档里面含有修订所以会导致表格删除不成功。 去除word文档里面的修订就3句话: Dispatch.put(wordFile,"TrackRevisions",new Variant(false)); Dispatch.put(wordFile,"PrintRevisions",new Variant(false)); Dispatch.put(wordFile,"ShowRevisions

qq_38133320的博客 1673

Java操作Word修订功能:启用、接受、拒绝、获取修订

在 Word 文档中启用跟踪更改功能后,会记录文档中的所有编辑行为,例如插入、删除、替换和格式更改。对插入或删除的内容,可通过本文中介绍的方法来获取。在IDEA中打开如下界面,将本地路径中的jar文件引入Java程序:方法2通过 Maven 仓库下载。的修订功能是一种在文档中进行编辑和审阅的功能。修订模式启用后,所有对文档的修改都将被跟踪记录,并使用各种颜色和样式突出显示在文本中。我们可以审阅文档中已修改的内容,并决定是否接受修订。启用修订功能中用到的最核心的方法为。插入文档中的内容或者从文档中删除的内。

Eiceblue的专栏 1847

[word] word2019文档中添加修订并对修订样式进行设置的方法 #媒体#媒体

5、打开“高级修订选项”对话框,单击“插入内容”下拉列表选择“双下划线”选项,将文档中的修改内容标记设置为双下划线,单击“修订行”下拉列表选择“右侧框线”选项,使修改行标记显示在行的右侧,完成设置后单击“确定”按钮,如图5所示。7、切换至“审阅”选项卡,在“修订”组内单击“显示标记”下拉按钮,在展开的标记选项内单击“批注框”下拉按钮,然后在展开的菜单列表内单击“在批注框中显示修订”选项,如图7所示。6、返回“修订选项”对话框,单击“确定”按钮返回Word文档,即可看到修订标记发生改变,如图6所示。

2401_82854215的博客 1517

WORD接受修订,并修改修订后文字的颜色

插入的内容:变成蓝色文字并保留在文档中;删除的内容:直接被删除,不保留;所有修订:被清除,文档干净整洁。

花藻の博客 3082

Word2019文档中修订功能

1、启用修订 打开原始文件,在“审阅”选项卡下单击修订”组中的“修订”下三角按钮,在展开的下拉列表中单击“修订”选项。 2、查看修订文本的效果 此时进入修订状态,用户可以参照批注对文档进行修改,删除和插入的内容均会被标记出来。 3、打开审阅窗格 如果要查看修订的内容,可在“修订”组中单击“审阅窗格”右侧的下三角按钮,在展开的下拉列表中选择审阅窗格样式,如单击“垂直审阅窗格”选项。 4、查看修订的明...

小坚果的博客 1932
上一篇: <<展现C#>>第四章 C#类型(修订)
下一篇: <<展现C#>>第六章 控制语句(修订)
rainbow
博客等级 码龄27年 3粉丝 12原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值