设计类型
类型和成员基础
基础概念
-
友元程序集
// 在程序集中的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的帮助文档 */
-
分部类、结构和接口
主要的作用:
- 源代码控制,方便协作
- 同一个文件中将类或结构分解成不同的逻辑单元
- 代码拆分
注意:
- 编译时候会合并,对CLR不可见。
-
继承的内存分配(面试基本都会考)
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); // 不可见 } } }
-
修饰符
- 注意:protected对于子类都是private(对外来说不可见),但是对于继承链来说,父类都是protected,子类是private。
IL深化(后续补)
- 调用虚方法等
参考内容
常量和字段
常量
- 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; } }
参考内容
方法
基础概念
-
实例构造器与类(引用类型)
-
实例化过程不调用构造参数的办法
- Object.MenberwiseClone
- 反序列化
-
注意:不要再构造函数中调用虚方法,避免实例字段没有实例化导致的麻烦。
-
-
实例构造器和结构(值类型)
- C#编译器不允许定义无参构造器
-
类型构造器(静态构造器)
- 线程安全
-
操作符重载
- implicit:告诉编译器为了生成代码来调用方法,不需要在源代码中进行显示转型。
- explicit:告诉编译器只有在发现了显示转型时,才调用方法。
// 参考例子
public class PersonTest : IEquatable<PersonTest>
{
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);
/// <summary>
/// 隐式转换
/// </summary>
/// <param name="p"></param>
public static implicit operator PersonTest(Person p){
return new PersonTest { Id = p.Id };
}
/// <summary>
/// 显示转换成其他对象
/// </summary>
/// <param name="p"></param>
public static explicit operator Person(PersonTest p)
{
return new Person { Id = p.Id };
}
}
public class Person
{
public string Id { get; set; }
}
-
扩展方法
-
分部方法
partial class E { private string _name; partial void OnNameChange(string value); public string Name { get { return _name; } set { OnNameChange(value.ToUpper()); _name = value; } } } ///// <summary> ///// 如果没有实现分部方法,IL里面不会进行调用 ///// </summary> //partial class E { // partial void OnNameChange(string value) // { // Console.WriteLine(""); // } //} class E1 : E { }
- 规则和原则
- 只能在分部类或结构中声明
- 分部方法的返回类型始终是void,任何参数都不能用out修饰符来标记。
- 分部方法的声明和实现必须具有完全一致的签名,定制特性会例外,??
- 如果没有对应的实现部分,不能再代码中创建一个委托来引用这个分部方法。
- 分部方法总是被视为private方法。
- 规则和原则
参数
基础概念
-
规则和原则
- 可为方法、构造器方法和有参属性(C#索引器)的参数指定默认值。
- 可为委托定义一部分的参数指定默认值。
- 有默认值的参数必须放在没有默认值的所有参数之后。
- 默认值必须是编译时能确定的常量值。
- 不要重命名参数变量,调用方可能会使用命名参数,可能会提示CS1739的错误。
- 如果方法从模块外部掉哟那个,更改参数的默认值具有潜在的危险性。
- 如果参数用ref或out关键字进行标识,就不能设置默认值。
- 可选或命名参数调用方法,附加规则:
- 实参可以按人仪顺序传递,但命名实参智能出现在实参的尾部。
- 可按名称将实参传给没有默认值的参数。
- C#不允许省略逗号之间的实参。
- 如果参数要求ref/out,为了以传参数名的方式传递实参。
-
隐士类型的局部变量
-
ref和out关键字
-
向方法传递可变数量的参数(params)
-
参数和返回类型的设计规范
- 说明方法的参数类型时,应尽量指定最弱的类型。
- 相反,一般最好是将方法的返回类型声明为最强的类型。
属性
基础概念
- System.Tuple
- 有参属性
网友评论