跟小静读CLR via C#(05)- 访问限定、数据成员

简介:

跟小静读CLR via C#(05)- 访问限定、数据成员

今天跟大家分享一下关于访问限定和数据成员的知识。主要包括以下两点:

  • Abstract, sealed, virtual, new, override怎么用?
  • Const 和 readonly好像都表示不能改变的值,有什么区别呢?

一、 访问限定

类和方法有一些访问限定符,如private,public等。除此之外,还包含一些预定义特性。下面几个你都知道吗?

1. 类的预定义特性
  • Abstract——抽象类。不能实例化。如果派生类不是抽象类,则必须实例化。
  • Sealed——密封类。不能做基类被继承。

要想既不能实例化又不能被继承? 需要两个步骤:

  1. seadled修饰。防止被被继承 ;
  2. 私有化无参构造器。防止实例化自动调用默认无参构造函数。例如:

sealed class Demo

{

private Demo() { }

...

}

2. 方法的预定义特性
  • Abstract——用于抽象类中的抽象方法,该方法不能包含具体实现。派生类如果不实现该方法,则必须为抽象类。

public abstract class Animal

{

public abstract void Shout();

}

  • Virtual——用于非静态方法。调用时实现的是继承链最末端的方法,也就是按照运行时类型调用的,而不是编译时类型。
  • New——隐藏但并不会改变基类中虚方法的实现。
  • Override——重写基类中的虚方法。

实例: 

public class Animal //基类
    {

        public virtual void Shout()     //定义虚方法
        {

            Console.WriteLine("逼我发威啊!"); //虚方法实现

        }

    }

    public class Dog : Animal  
    {

        public override void Shout()    //override重写基类方法
        {

            Console.WriteLine("汪汪!");

        }

    }

    public class Cat : Animal
    {

        public new void Shout()        //new隐藏基类方法
        {

            Console.WriteLine("喵喵~~");

        }

    }

class Program
    {

        static void Main(string[] args)
        {

            new Animal().Shout();   //“逼我发威啊!”

            new Dog().Shout();        //“汪汪!”

            new Cat().Shout();        //”喵喵~~”

            Console.WriteLine("**************************");

            Animal a1 = new Dog();

            a1.Shout();                  //重写了基类的方法   “汪汪!”

            Animal a2 = new Cat();

           ((Cat)a2).Shout();       //派生类中的方法隐藏了基类的方法 ”喵喵~~”

            a2.Shout();                 //基类的方法没有被修改,只是被隐藏 “逼我发威啊!”         

            Console.Read();

        }

    }

image

 

二、 数据成员——常量和只读

我们经常提到常量和只读,听上去都是不能改变的意思,那么它们到底有什么区别呢?
1. 常量const

常量是恒定不变的,在编译时就确定了它的值,编译后直接将值存到元数据中。变量类型只能是编译器能直接识别的基元类型,不能是引用类型,因为引用类型需要在运行时调用构造函数进行初始化。

我们给段代码实际看一下:

class TestConst    
   {
       public const int Number = 100;     //声明常量并赋值
       public string GetName()
       {
           const string name = "XiaoJing";    //常量用作局部变量
           return name;
       }
   }

   class Program
   {

       static void Main(string[] args)
       {
           Console.WriteLine("const:The total number is " + TestConst.Number);
           Console.Read();
       }
   }

通过ILDasm工具查看一下,const变量编译后为static literal类型,所以不难理解,常量是针对类的一部分,而不是实例的一部分。这样它才能保证是恒定不变的。

image

在使用常量时,编译器从常量模块的元数据中直接取出常量的值,嵌入到IL代码中。所以在声明常量的时候就要为它初始化值。例如上面的例子,Number直接替换为值100。

Main函数的IL代码如下:

image

还有一点,const常量也可以用于局部变量,例如上面的GetName()方法。

2. 只读字段readonly

类的数据成员通常用来保存一个值类型的实例或者指向引用类型的引用,CLR支持读写和只读两种数据成员,其中只读数据成员使用readonly修饰的。看个实际例子:

class TestReadonly
  {
      public readonly int Number = 100;   //只读实例成员
      public TestReadonly()
      {
          Number = 200;         //构造器中重新赋值
      }
  }

  class Program
  {
      static void Main(string[] args)
      {
          Console.WriteLine("readonly:The total number is " + new TestReadonly().Number);
          Console.Read();
      }
  }

通过ILDasm.exe工具查看, Readonly实例成员编译后为 initonly修饰。这个例子是只读实例成员,readonly也可以修饰静态只读成员,需要在类静态构造器中初始化,这里就不赘述了。

image

数据成员是在类的实例构造过程中分配内存的,因此能在运行时刻获取它的值。因此只读成员类型没有限制,可以是基元类型,也可以是引用类型。而且可以在构造器中赋值,声明时赋值与否都可以。

我们查看main函数的IL代码:可以看出首先对TestReadonly类进行实例化,然后读取实例的Number成员的值,是在运行过程中获取值的。

image

还有要注意的一点,readonly不能用作局部变量,否则编译时就会报错。

最后我们要说明的是,readonly字段不能改变的是引用,而不是字段引用的对象。例如

 

复制代码
public   sealed   class  AType
    {
        
public   static   readonly   char [] chars  =   new   char [] {  ' a ' ' b ' ' c '  };
    }
    
public   sealed   class  BType
    {
        
public   static   void  Change()
        {
            AType.chars[
0 =   ' X ' ;
        }
    }
    
class  Program
    {
        
static   void  Main( string [] args)
        {
            Console.WriteLine(AType.chars[
0 ]);
            BType.Change();
            Console.WriteLine(AType.chars[
0 ]);
            Console.Read();
        }
    }
复制代码

运行结果是

a

X






    本文转自 陈敬(Cathy) 博客园博客,原文链接:

http://www.cnblogs.com/janes/archive/2011/07/11/2103215.html

,如需转载请自行联系原作者


相关文章
|
2月前
|
C#
一文搞懂C#中类成员的可访问性
一文搞懂C#中类成员的可访问性
39 5
|
2月前
|
测试技术 API C#
C#使用Bogus生成测试数据
C#使用Bogus生成测试数据
39 1
|
22天前
|
存储 C# 开发者
枚举与结构体的应用:C#中的数据组织艺术
在C#编程中,枚举(`enum`)和结构体(`struct`)是非常重要的数据类型。枚举用于定义命名常量集合,提高代码可读性;结构体则封装相关数据字段,适合小型数据集。本文从基本概念入手,探讨它们的使用技巧、常见问题及解决方案,帮助开发者更好地利用这些特性构建健壮的应用程序。
23 8
|
1月前
|
SQL 存储 关系型数据库
C#一分钟浅谈:使用 ADO.NET 进行数据库访问
【9月更文挑战第3天】在.NET开发中,与数据库交互至关重要。ADO.NET是Microsoft提供的用于访问关系型数据库的类库,包含连接数据库、执行SQL命令等功能。本文从基础入手,介绍如何使用ADO.NET进行数据库访问,并提供示例代码,同时讨论常见问题及其解决方案,如连接字符串错误、SQL注入风险和资源泄露等,帮助开发者更好地利用ADO.NET提升应用的安全性和稳定性。
65 6
|
2月前
|
存储 C# 数据库
解决C#对Firebase数据序列化失败的难题
在游戏开发中,Unity结合Firebase实时数据库为开发者提供强大支持,但在C#中进行数据序列化和反序列化时常遇难题。文章剖析了数据丢失或反序列化失败的原因,并给出解决方案,包括使用`JsonUtility`、确保字段标记为`[Serializable]`以及正确配置网络请求。示例代码演示了如何在Unity环境中实现Firebase数据的序列化和反序列化,并通过设置代理IP、Cookies和User-Agent来增强网络请求的安全性。这些技巧有助于确保数据完整传输,提升开发效率。
解决C#对Firebase数据序列化失败的难题
|
2月前
|
数据库
C#Winform使用NPOI获取word中的数据
C#Winform使用NPOI获取word中的数据
132 2
|
2月前
|
缓存 NoSQL Redis
【Azure Redis 缓存】C#程序是否有对应的方式来优化并缩短由于 Redis 维护造成的不可访问的时间
【Azure Redis 缓存】C#程序是否有对应的方式来优化并缩短由于 Redis 维护造成的不可访问的时间
|
2月前
|
开发框架 .NET C#
WPF/C#:显示分组数据的两种方式
WPF/C#:显示分组数据的两种方式
43 0
|
2月前
|
XML C# 数据格式
WPF/C#:如何将数据分组显示
WPF/C#:如何将数据分组显示
33 0
|
2月前
|
C# Windows
WPF/C#:如何显示具有层级关系的数据
WPF/C#:如何显示具有层级关系的数据
38 0