zoukankan      html  css  js  c++  java
  • 一起谈.NET技术,漫谈.Net关键字系列之一Sealed与Final 狼人:

          Sealed与Final修饰符其实并不是一个语言平台的产物,他们有着各自所属的语言环境,但这两个关键字都是.Net平台中不可或缺的,那么二者用法几何,随本文一探究竟。

      一.Sealed

          sealed 修饰符可以应用于类、实例方法和属性。用于类时,该类被称为密封类,密封类不能被继承;用于方法时,该方法被称为密封方法,密封方法会重写基类中的方法;sealed修饰符应用于方法或属性时,必须始终与override一起使用;结构是隐式密封的,因此它们不能被继承。

        ● 描述方法:

    //Error: cannot be sealed because it is not an override
    public sealed string func()
    {
    return "";
    }

    //OK
    public sealed override string func()
    {
    return "";
    }

       ● 继承中的方法:(TestChild2中无法重写任何方法)

    clip_image002

        ●描述属性:

    public sealed override double Hours
    {
    get { return 0.1; }
    set { }
    }

      ●描述变量:

    //Error The modifier 'sealed' is not valid for this item
    sealed override string a;

      ●描述接口:

    interface Itesta
    {
    //Error cannot be sealed because it is not an override
    sealed string Geta();
    }

      ● sealed能提高性能优化?

      有一些朋友认为当元素被标记为sealed时,有助于系统运行性能的提升,其理由有2:

          1.有助于JIT内联。

          2.消除了协变与逆变和后期绑定,使CLR直接执行这个实例。

          这看似是有些道理的,可这样做又会提升多少性能,提升性能的同时又损失了什么呢?

          先说说“第1点”,促使JIT内联代码的因素有很多,JIT不会因为一个类是sealed,就去装入其中的内容(详见.Net Discovery 系列之六--深入浅出.Net实时编译机制(下),这也不符合程序局部性原理。http://www.cnblogs.com/isline/archive/2009/12/27/1633453.html),而对于sealed类内部方法的内联,很大原因也是由于方法槽映射关系决定的,sealed作用有待考证。

          第2点,由于sealed类不可派生或被继承,所以的确在运行时省去可CLR一些额外的工作,但是这些工作只是一些类似于“寻址”的工作,因为虚拟方法表已经完成了运行时与编译时的对应关系,纯粹的运行时只是在寻找这些关系而已,所以sealed省去的只是一部分较为复杂的寻址关系,因为即使没有继承,也不可避免一个方法表的应用。

          而这样做又损失了什么呢?大家想想,面向对象的原因是什么?是提升性能吗?显然不是,面向对象的只是高级语言层面的,最终运行的代码都是以顺序流程的方式出现,面向对象的本质是“抽象”,它解决的是软件产品的“控制”问题,变不可控为可控,变不可预测的风险为可预测的风险,所以如果因为要提升性能,而把大部分类都sealed化,岂不是大大削弱了面向对象的抽象能力呢?

          ● Sealed不能同时abstract?

          也许在高级语言中抽象须实现与密封不可继承是一对矛盾者,但IL暴露了一些不一样的细节,让我们来分析一下这段IL代码:

    clip_image004

          这段代码简单得很,就是声明了一个类,然而这个类却是abstract和Sealed的,猜猜这个类用了什么修饰符修饰它?

    好吧,其实高级语言中对应的修饰符就是static。

    clip_image005

          static类不能被实例化(abstract)亦不可派生(Sealed),我想abstract同时Sealed也未尝不可,但这样做会使语义出现二义性,为避免这种效果才规定在编辑器中不可abstract+Sealed,static修饰类的初衷我想也是如此,实际上static在修饰类时,就是一个包含了实现的abstract+Sealed的类,这个类不能被实例化也不能派生出新的类。

      二.Final

          final修饰符来限定变量、字段、方法和类。用于变量时,该变量只能赋值一次,不可修改;用于方法时,该方法不能被重写或隐藏;用于类时,该类不能被继承。

          接口的成员是不能使用该关键字的,道理和不能在abstract类使用final一样。

          值得一提的是,如果使用final修饰类中的字段,那么该字段必须在构造函数中赋值,否则使用类实例调用的方式是无法对该字段进行赋值的,道理很简单,类在实例化时,会为每一个成员字段赋初值,之后你如果再通过实例方式调用该final字段,就属于二次赋值的情况了,这种情况是不允许的。在构造函数中为final变量赋值的方法叫做“延时赋值”(Java),相应的final变量叫做“空白final”(Java)。

          Final并不是一个C#中的关键字,但经常在C#面试题中出现,例如说说“Final、Finally、finalize的区别”,其实这已经超出C#的范畴,这三个关键字分别考核了J#、.Net 容错方法、.Net垃圾收集机制,奇怪的是,每次我面试C#程序人员时,大部分人员对Final这个关键字并无陌生之感,相反却答得头头是道,看来来面试之前,早在网上有所预习,呵呵。

          例子(摘自MSDN,已做翻译):

    public class Value
    {
    public int i = 1;
    }
    public class FinalData
    {
    //可认为等同于编译时常量
    final
    int i1 = 9;
    static final int i2 = 99;
    //public 常量:
    public static final int i3 = 999;
    //不可作为编译时常量:
    final
    int i4 = (int)(Math.random() * 11);
    static final int i5 = (int)(Math.random() * 11);
    Value v1
    = new Value();
    final Value v2
    = new Value();
    static final Value v3 = new Value();
    // 数组:
    final
    int[] a = { 1, 2, 3, 4, 5, 6 };
    public void print(String id)
    {
    System.
    out.println(id + ": " + "i4 = " + i4 + ", i5 = " + i5);
    }
    public static void main(String[] args)
    {
    FinalData fd1
    = new FinalData();
    // Error: Can't change value! (i1被描述为fianl的)
    // fd1.i1++;
    // OK. Object isn't constant(虽然v2是fianl的,但其中的变量并不受此约束)
    fd1.v2.i
    ++;
    // OK. Not final.
    fd1.v1
    = new Value();
    for (int i = 0; i < fd1.a.length; i++)
    {
    fd1.a[i]
    ++; // OK. Object isn't constant.(与上面那个v2一样,数组是final的,但数组元素不受约束)
    }
    // Error: Can't change handle! (v2是final的)
    // fd1.v2 = new Value();
    // Error: Can't change handle! (v3是static final的,等同于常量)
    // fd1.v3 = new Value();
    // Error: Can't change handle!(数组本身是final,不可new)
    // fd1.a = new int[3];
    fd1.print(
    "fd1");
    System.
    out.println("Creating new FinalData");
    FinalData fd2
    = new FinalData();
    fd1.print(
    "fd1");
    fd2.print(
    "fd2");
    }
    }

      答案:

      fd1: i4 = 0, i5 = 7

      Creating new FinalData

      fd1: i4 = 0, i5 = 7

      fd2: i4 = 8, i5 = 7

      总结:final是J#中的一种修饰符,在VS2008及以后版本中就放弃J#了,它与sealed不同的是fianl可以修饰变量,而sealed则不能,不过你可以通过readonly关键字来实现。

          关于二者对性能的提升作用,我认为有待考证,从理论层面来讲,为难以证明的性能因素而特意使用此关键字有些得不偿失。

  • 相关阅读:
    redis安装
    redis的使用场景和基本数据类型
    (传输层)tcp协议
    async/await
    Promise对象
    对称加密与非对称加密
    Js遍历数组总结
    HTTPS加密传输过程
    HTML节点操作
    Js的new运算符
  • 原文地址:https://www.cnblogs.com/waw/p/2162679.html
Copyright © 2011-2022 走看看