设计类型(二)

设计类型

类型和成员基础

基础概念

  1. 友元程序集

    // 在程序集中的AssemblyInfo.cs中,标记友元程序集名称和公钥的字符串参数(如果程序集没有强签名的话,则只要程序集即可)
    [assembly: InternalsVisibleTo("Friend2")]
    [assembly: InternalsVisibleTo("Friend3,PublicKey=xxxxx")]
    // 其中PublicKey来源:
    /*
    1. 创建强类型签名文件test.pfx
    2. 打开cmd窗口,并cd到该目录
    3. 创建强签名文件:sn -k test.snk
    4. 创建私钥: sn -p test.snk test.pk.snk
    5. 提取公钥: sn -pt test.pk.snk
    具体参考蒋金楠老师的博客,或者查看sn的帮助文档
    */
    
  1. 分部类、结构和接口

    主要的作用:

    • 源代码控制,方便协作
    • 同一个文件中将类或结构分解成不同的逻辑单元
    • 代码拆分

    注意:

    • 编译时候会合并,对CLR不可见。
  2. 继承的内存分配(面试基本都会考)

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace ILLearning
    {
        internal class C
        {
            protected string Hello = "world";
            public virtual void Say(){
                Console.WriteLine("C");
            }
        }
    
        internal class C1 : C
        {
            protected string HelloWorld = "Hello,Kitty";
            protected int i = 4;
            public override void Say()
            {
                var that =  this;
                Console.WriteLine("C1:"+this.i);
                Console.WriteLine("C1:"+that.HelloWorld);
                Console.WriteLine("C1");
            }
        }
    
        internal class C2 : C1
        {
            public override void Say()
            {
             this.Hello = "Kitty";
             this.i = 6;
                this.HelloWorld = "Hello,World";
                Console.WriteLine("C2:"+this.HelloWorld);
                Console.WriteLine("C2:"+this.i);
                /*
                * 主要易错点在这里:
                * 正确的答案是:i=6,HelloWorld="Hello,World",原因是C1的对象没有在这里并没有分配到内存,因此this还是指向C2的对象。
                */
                base.Say();
                Console.WriteLine("C2");
            }
        }
    
        public class CTest
        {
            public static void Main()
            {
                C c = new C2();
                // 分析过程:
                /*
                * new C2()// 开辟C2的堆内存,并向上创建到System.Object,这时候在内存里面已经有Object和C和C1和C2的类型对象
                * 将c指向C2的堆的内存空间,但是只是含有C的公开或者静态的方法或者属性、字段。
                 */
                c.Say();
                // Console.WriteLine(c.Hello); // 不可见
            }
        }
    }
    
  3. 修饰符

    • 注意:protected对于子类都是private(对外来说不可见),但是对于继承链来说,父类都是protected,子类是private。

IL深化(后续补)

  • 调用虚方法等

参考内容

  1. 蒋金楠老师的当InternalsVisibleToAttribute特性遭遇"强签名"

常量和字段

常量

  • const定义的常量总是隐式为static。

字段

重要的字段修饰符

  • Volatile(后续补上)

    
    
  • static和readonly

        public class D
        {
            // 运行时进行初始化,并且不能修改
            public static readonly string a = "test";
            // 可读可写,但是不为对象独有
            private static Int32 b = 0;
            // 在构造函数里面能改变值,不能改变的是引用
            private readonly string c = "test2";
            public D()
            {
                //a = "test1";// 
                this.c = "test3";
            }
    
            public void OutPut() {
                //this.c = "test4";
                b = 5;
            }
        }
    

参考内容

方法

基础概念

  1. 实例构造器与类(引用类型)

    • 实例化过程不调用构造参数的办法

      • Object.MenberwiseClone
      • 反序列化
    • 注意:不要再构造函数中调用虚方法,避免实例字段没有实例化导致的麻烦。

  2. 实例构造器和结构(值类型)

    • C#编译器不允许定义无参构造器
  3. 类型构造器(静态构造器)

    • 线程安全
  4. 操作符重载

    • implicit:告诉编译器为了生成代码来调用方法,不需要在源代码中进行显示转型。
    • explicit:告诉编译器只有在发现了显示转型时,才调用方法。
  // 参考例子
  public class PersonTest : IEquatable
  {
      public PersonTest()
      {
      }

      public string Id { get; set; }

      public string Name { get; set; }

      public bool Equals(PersonTest other)
      {
          return !object.ReferenceEquals(null, other) && object.ReferenceEquals(this, other);
      }

      public override bool Equals(object obj)
      {
          if (obj == null) return false;
          if (obj.GetType() != this.GetType()) return false;
          return Equals(obj as PersonTest);
      }

      public override int GetHashCode()
      {
          return string.IsNullOrEmpty(this.Id) ? 0 : this.Id.GetHashCode();
      }

      // 默认成对出现,并且一般要覆写Equals和GetHashCode
      public static bool operator ==(PersonTest a, PersonTest b) => !object.ReferenceEquals(null,a) && object.ReferenceEquals(a, b);
      public static bool operator !=(PersonTest a, PersonTest b) => object.ReferenceEquals(null,a) || object.ReferenceEquals(null,b) || !object.ReferenceEquals(a, b);

      /// 
      /// 隐式转换
      /// 
      /// 
      public static implicit operator PersonTest(Person p){
          return new PersonTest { Id = p.Id };
      }

      /// 
      /// 显示转换成其他对象
      /// 
      /// 
      public static explicit operator Person(PersonTest p)
      {
          return new Person { Id = p.Id };
      }

  }

  public class Person
  {
      public string Id { get; set; }
  }
  1. 扩展方法

  2. 分部方法

        partial class E
        {
            private string _name;
            partial void OnNameChange(string value);
    
    
            public string Name
            {
                get { return _name; }
                set
                {
                    OnNameChange(value.ToUpper());
                    _name = value;
                }
            }
        }
    
        ///// 
        ///// 如果没有实现分部方法,IL里面不会进行调用
        ///// 
        //partial class E {
        //    partial void OnNameChange(string value)
        //    {
        //        Console.WriteLine("");
        //    }
        //}
    
        class E1 : E
        {
            
        }
    
    • 规则和原则
      • 只能在分部类或结构中声明
      • 分部方法的返回类型始终是void,任何参数都不能用out修饰符来标记。
      • 分部方法的声明和实现必须具有完全一致的签名,定制特性会例外,??
      • 如果没有对应的实现部分,不能再代码中创建一个委托来引用这个分部方法。
      • 分部方法总是被视为private方法。

参数

基础概念

  1. 规则和原则

    • 可为方法、构造器方法和有参属性(C#索引器)的参数指定默认值。
    • 可为委托定义一部分的参数指定默认值。
    • 有默认值的参数必须放在没有默认值的所有参数之后。
    • 默认值必须是编译时能确定的常量值。
    • 不要重命名参数变量,调用方可能会使用命名参数,可能会提示CS1739的错误。
    • 如果方法从模块外部掉哟那个,更改参数的默认值具有潜在的危险性。
    • 如果参数用ref或out关键字进行标识,就不能设置默认值。
    • 可选或命名参数调用方法,附加规则:
      • 实参可以按人仪顺序传递,但命名实参智能出现在实参的尾部。
      • 可按名称将实参传给没有默认值的参数。
      • C#不允许省略逗号之间的实参。
      • 如果参数要求ref/out,为了以传参数名的方式传递实参。
  2. 隐士类型的局部变量

  3. ref和out关键字

  4. 向方法传递可变数量的参数(params)

  5. 参数和返回类型的设计规范

    • 说明方法的参数类型时,应尽量指定最弱的类型。
    • 相反,一般最好是将方法的返回类型声明为最强的类型。

属性

基础概念

  1. System.Tuple
  2. 有参属性

你可能感兴趣的:(设计类型(二))