一:多项目开发
1、一个类必须声明为public才能被别的项目(程序集)访问,不写或者写internal,那么这个类就不能被别的项目访问,只有当前程序集内部可以访问。
二:索引器
没有名字,索引器的内部本质(ILSpy的IL模式下看)类型 this[参数]{get;set;}
可以是只读或者只写(在get或者set前加上private)
字符串是只读索引,因此不能对字符串中的某个字符进行从新赋值,即只能char ch = s[5];不能s[5]=‘a’。
开发中自己写的机会很少,一道面试题:C#中索引器是否只能根据数字进行索引?是否允许多个索引器参数?答案:可以进行非数字索引,可以允许多个参数进行索引。
internal class Program
{
static void Main(string[] args)
{
Person p1 = new Person();
p1[3, 5] = "hello";
string s = p1[1, 2];
//Dictionary<int, string> d = new Dictionary<int, string>();
//d[3] = "";
Console.WriteLine(s);
Console.ReadLine();
}
}
class Person
{
public string this[int x, int y]
{
get
{
return "" + x + y;
}
set
{
Console.WriteLine("x = " + x + "; y = " + y + "; value = " + value);
}
}
}
三:密闭类和静态类
1、密闭类是修饰为sealed的类,sealed不能有子类。一般只有系统中的一些基本类声明为sealed。面试题:是否可以编写一个类继承自String类?
答:不能,因为string被声明为了sealed了。
2、静态类:声明为static的类,不能实例化,只能定义static成员。通常用作定义扩展方法。
3、C#3.0特性:扩展方法。声明静态类,增加一个静态方法,第一个参数是被扩展类型的标记为this,然后在其他类中可以直接调用,本质上还是对静态方法调用提供的一个“语法糖”,也可以用普通静态方法的方式调用,所以不能访问private和protected成员。例子:给String扩展一个IsEmail方法。自己写的机会比较少。
internal class Program
{
static void Main(string[] args)
{
//string e = "abc@163.com";
//bool b = Person.IsEmail(e);
//bool b = e.IsEmail();
string s1 = "abcd";
string s2 = s1.Repeat(3);
Console.WriteLine(s2);
Console.ReadLine();
}
}
static class Person
{
//扩展方法
public static bool IsEmail(this string s)
{
return s.Contains("@");
}
public static string Repeat(this string s, int count)
{
string result = "";
for (int i = 0; i < count; i++)
{
result += s;
}
return result;
}
//public static bool IsEmail(string s)
//{
// return s.Contains("@");
//}
}
四:深拷贝、浅拷贝
如果拷贝的时候共享被引用的对象就是浅拷贝,如果被引用的对象也拷贝一份出来就是深拷贝。(深拷贝就是说重新new一个对象,然后把之前的那个对象的属性值在重新赋值给这个用户)。
internal class Program
{
static void Main(string[] args)
{
//Person p1 = new Person();
//p1.Name = "lilei";
//p1.Age = 10;
//Person p2 = p1; //让p2指向p1当前所指向的对象
拷贝、克隆:Clone
//Person p3 = new Person(); //拷贝了一份出来
//p3.Name = p1.Name;
//p3.Age = p1.Age;
//p1.Age = 20;
Dog dog = new Dog();
dog.Name = "wangcai";
Person p1 = new Person();
p1.Name = "lilei";
p1.Age = 10;
p1.Dog = dog;
Person p2 = new Person();
p2.Name = p1.Name;
p2.Age = p1.Age;
p2.Dog = (Dog)p1.Dog.Clone();
p1.Dog.Name = "erdan";
Console.WriteLine(p2.Dog.Name);
Console.ReadLine();
}
}
class Dog
{
public string Name { get; set; }
public object Clone()
{
return this.MemberwiseClone();
}
}
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Dog Dog { get; set; }
}
五:结构体
在平时的开发中很少自己去写结构体,是一种值类型的数据。对于结构,不像类那样存在继承,一个结构体不能从另一个结构或类继承。但是结构体从基类Object继承。
internal class Program
{
static void Main(string[] args)
{
Person p1 = new Person();
p1.Name = "lilei";
p1.Age = 10;
Person p2 = p1;
p1.Age = 20;
Console.WriteLine(p2.Age);
Dog d1 = new Dog();
d1.Name = "lilei";
d1.Age = 10;
Dog d2 = d1;
d1.Age = 20;
Console.WriteLine(d2.Age);
Console.ReadLine();
}
}
struct Dog
{
public string Name { get; set; }
public int Age { get; set; }
}
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
六:值类型和引用类型
什么是“引用类型”:引用类型派生自System.Object
什么是“值类型”:值类型均隐式派生自System.ValueType(ValueType其实也是继承自Object,不过是特立独行的一个分支)
值类型有哪些:数值类型(int、long、double、float、char)、bool、结构体、枚举。(在平时写的时候看不到Int32继承自ValueType的原因是编译器帮我们进行了处理)
引用类型有哪些字符串、数组、类、接口等
区别(主要):
引用类型变量的赋值只复制对对象的引用;引用类型在堆内存(malloc);
值类型变量赋值会拷贝一个副本;值类型在栈内存;值类型一定是sealed;
string是const引用,所以复制时,会拷贝副本,效果和值类型类似。
七:CTS、CLS、CLR
1、 .Net平台下不只有C#语言,还有VB.Net、F#等语言。IL是程序最终编译的可以执行的二进制代码(托管代码),不同的语言最终都编译成标准的IL(中间语言,MSIL);这样C#可以调用VB.Net写的程序集(Assembly,dll、exe)。在.Net平台下:不同语言之间可以互联互通、互相调用。
2、不同语言中的数据类型各不相同,比如整数类型在VB.Net中是Integer、C#中是int。.Net平台规定了通用数据类型(CTS,Common Type System),各个语言编译器把自己语言的类型翻译成CTS中的类型。int是C#中的类型,Int32是CTS中的类型;int是C#的关键字,Int32不是。
面试题:
string和String的区别是什么?答:string是C#语言的中的类型,String是CTS中的类型
int和Int32的区别是什么? 答:int是C#中的类型,Int32是CTS中的数据类型
3、不同语言的语法不一样,比如定义一个类A继承自B的C#语法是class A:B{},VB.Net的语法是Class A Inherits B。.Net平台规定了通用语言规范(CLS, Common Language Specification )。
4、IL代码由公共语言运行时(CLR, Common Language Runtime )驱动运行,CLR提供了垃圾回收(GC, Garbage Collection,没有任何引用的对象可以被自动回收,分析什么时候可以被回收)、JIT(即时编译器)。
5、值类型是放在“栈内存”中,引用类型放到“堆内存”,栈内存会方法结束后自动释放,“堆内存”则需要GC来回收。
八:拆箱、装箱
值类型赋值给Object类型变量的时候,会发生装箱:包装成Object。ValueType不也是继承自Object吗(CLR内部处理);
Object类型变量赋值给值类型赋值的时候会发生拆箱,需要做显式转换。
下面几句代码有没有错,解释一下内存是怎么变化的
int i=10;
object obj = i; //装箱 内存由栈内存转为堆内存
int j = obj; //错误
long j = (long)obj; //错误 拆箱的时候要和装箱时的数据类型一样
int j = (int)obj; //拆箱 内存由堆内存转为栈内存
拆箱的时候一定要用装箱的类型
九:关于相等 Equals
查看判断两个对象是否是同一个对象要用:object.ReferenceEquals();
因为"==",默认值是比较两个对象是不是同一个对象。所以有时候两个对象的内容相等,但是比较后还是false。
Object的Equals方法也比较两个变量指向的是否同一个对象;对象如果override了Equals方法,就可以进行内容的相同比较。
默认情况下不是调用Equals方法,需要重载"=="运算符;String等这些类是重写了Equals方法。
面试题:下面的代码有几个字符串对象
String s1 = “abc”;
string s2 = s1;
string s3 = new String(new char[] {‘a’,‘b’,‘c’ });
Console.WriteLine(s1==s3); //输出true(虽然这里s1和s3不是同一个对象,但由于string重写了Equals方法,只要内容相等也返回true)
答案:两个字符串对象
namespace Test
{
internal class Program
{
static void Main(string[] args)
{
Person p1 = new Person();
p1.Name = "lilei";
p1.Age = 10;
Person p2 = p1;
Person p3 = new Person();
p3.Name = "lilei";
p3.Age = 10;
Console.WriteLine(object.ReferenceEquals(p1, p2));
Console.WriteLine(object.ReferenceEquals(p1, p3));
Console.WriteLine(p1 == p2);
Console.WriteLine(p1 == p3);
//object的Equals方法的默认实现是比较两个变量是否是同一个对象。
string s1 = "abc";
string s2 = s1;
string s3 = new string(new char[] { 'a', 'b', 'c' });
Console.WriteLine(object.ReferenceEquals(s1, s2));
Console.WriteLine(object.ReferenceEquals(s1, s3));
Console.WriteLine(s1 == s2);
Console.WriteLine(s1 == s3);
Console.ReadLine();
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public override bool Equals(object? obj)
{
if (!(obj is Person))
{
return false;
}
else
{
Person person = (Person)obj;
if (this.Name == person.Name && this.Age == person.Age)
{
return true;
}
else
{
return false;
}
}
}
public static bool operator ==(Person left, Person right)
{
return left.Equals(right);
}
public static bool operator !=(Person left, Person right)
{
return !left.Equals(right);
}
}
}
十:字符串暂存池(缓冲池)
字符串不可变性,字符串的“暂存池”两个特性。
字符串是引用类型,程序中会存在大量的字符串对象,如果每次都创建一个字符串对象,会比较浪费内存、性能低,因此CLR做了“暂存池”(拘留池、缓冲池、暂存池),在一些情况下对于字符串对象进行了重用。
namespace Test
{
internal class Program
{
static void Main(string[] args)
{
string s1 = "lilei";
string s2 = "lilei";
string s3 = "li" + "lei";
string s4 = new string(s1.ToCharArray());
string s5 = new string(new char[] { 'l', 'i', 'l', 'e', 'i' });
Console.WriteLine(Object.ReferenceEquals(s1, s2));
Console.WriteLine(Object.ReferenceEquals(s1, s3));
Console.WriteLine(Object.ReferenceEquals(s1, s4));
Console.WriteLine(Object.ReferenceEquals(s1, s5));
Console.WriteLine(Object.ReferenceEquals(s4, s5));
Console.ReadLine();
}
}
}
面试题:上面的代码有几个字符串对象?
答案:三个字符串对象s1、s2、s3是同一个字符串对象,在内容相同的情况下只有new才能产生一个新的字符串对象。
十一:ref、out
普通参数是“值类型传递拷贝,引用类型传递引用”,但是都不能在函数内部修改外部变量的指向,这时候要用ref或者out(相当于把变量都传进去了)。
他们作用不同:
ref的作用“方法内部修改外部变量的引用”;
out的作用“方法内部给外部变量初始化,相当于一个函数多个返回值”。
1、使用ref型参数时,传入的参数必须先被初始化,方法中可以不赋值。对out而言,必须在方法中对其完成初始化,方法外部不用初始化,初始化也没用。
2、使用ref和out时,在方法的参数和执行方法时,都要加ref或out关键字。以满足匹配。
3、out适合用在需要retrun多个返回值的地方,而ref则用在需要被调用的方法修改调用者的引用的时候。
应用:
1、交换两个变量的值;
2、int.TryParse():if(int.TryParse(s,out i)),返回值表示是否转换成功,out参数表示转换成功的值。
namespace NP
{
internal class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.Name = "lilei";
int i = 10;
//Test_1(person, i);
Test_2(ref person, ref i);
Console.WriteLine(person.Name);
Console.WriteLine(i);
Console.WriteLine("=====================");
int i1 = 1;
int i2 = 9;
Swap(ref i1, ref i2);
Console.WriteLine("i1 = " + i1 + ", i2 = " + i2);
int i3 = 1;
Test_3(out i3);
Console.WriteLine("i3 = " + i3);
string s = "456";
int i4;
if (int.TryParse(s, out i4))
{
Console.WriteLine("i4 = " + i4);
}
else
{
Console.WriteLine("不是合法整数!");
}
Console.ReadLine();
}
static void Test_3(out int i)
{
i = 9;
}
static void Swap(ref int i1, ref int i2)
{
int temp = i1;
i1 = i2;
i2 = temp;
}
static void Test_1(Person person, int i)
{
person.Name = "hanmeimei";
i = 20;
}
/// <summary>
/// ref就相当于被外部的变量传进来了,在函数内部可以改变外部变量的指向
/// </summary>
/// <param name="person"></param>
/// <param name="i"></param>
static void Test_2(ref Person person, ref int i)
{
person = new Person();
person.Name = "hanmeimei";
i = 20;
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
}