1、ref与out关键字
不同点:
ref:参数传入方法前,需要先赋值。
out:参数传入方法前,对赋值没有要求,但是方法内必须对参数赋值。
(如果没按要求赋值,编辑器会报错)
相同点:
ref与out标记参数时,方法内改变参数的值,变量的值也发生变化。
protected void Page_Load(object sender, EventArgs e) { string j="初始值"; refTest(ref j); Console.Write(j); //此时j="改变值",out与ref都会改变 变量 的值。 test(j); Console.Write(j); //此时j="改变值",test方法并没有将j的值改变 } static void refTest(ref string a) { a = "改变值"; } static void test(string a) { a = "改变不了"; }
2、委托与事件
场景:下一个页面(类)去执行上一个页面(类)的方法。(A页面跳转到B页面,在B页面通过事件的方法,触发A页面的方法运行。)
用于两个类中的某些方法不方便直接调用。
A页面:
B b=new B();
b.event +=new B.method(test); //事件可以添加多个,里面的方法会依次执行
b.main();
private void test()
{
//一些操作,例如girdView重新加载数据
}
B页面:
public delegate void method();
public event method event1;
public void main()
{
if(event1!=null)
{
event1(); //此时会触发A页面页面的test方法。
}
}
A页面:
public partial class delegate1 : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { Class1 cl = new Class1(); cl.shijian += new Class1.weituo(test); //将test方法委托 cl.test2(); //执行class1的test2方法 } private void test() { string a = "ceshi"; } }
B页面:
public class Class1 { public delegate void weituo(); public event weituo shijian; public void test2() { if (shijian != null) { shijian(); //此时去执行A页面里面test方法。 } } }
使用规范:
(1). 委托类型的名称都应该以 EventHandler 结束。
(2). 委托的原型定义:有一个void 返回值,并接受两个输入参数:一个Object 类型,一个EventArgs 类型(或继承自EventArgs)。
(3). 事件的命名为委托去掉 EventHandler 之后剩余的部分。
(4). 继承自 EventArgs 的类型应该以EventArgs 结尾。
3、泛型
class A<T> where T : class //T必须是一个class类型
{
public T a;
public T method(T t)
{
return t;
}
}
where T : struct | T必须是一个结构类型
where T : class T必须是一个类(class)类型
where T : new() | T必须要有一个无参构造函数
where T : NameOfBaseClass | T必须继承名为NameOfBaseClass的类
where T : NameOfInterface | T必须实现名为NameOfInterface的接口
4、重写与覆盖
父类使用virtual关键字修饰的方法(虚方法),子类中可以对父类中的虚方法进行重写(使用override关键字)
父类中不使用virtual修饰的方法(实方法),子类只能对父类中的实方法进行覆盖(使用new关键字)
虚方法、实方法都可以被覆盖(new)。
覆盖与重写的区别:
重写会改变父类方法的功能,覆盖不会改变父类方法的功能。
public class Class1 { public virtual string test1() //虚方法 { return "重写前"; } public string test2() //实方法 { return "覆盖前"; } } public class Class2 : Class1 { public override string test1() //使用关键字重写父类的test1方法 { return "重写后"; } public new string test2()//使用new关键字覆盖父类的test1方法 { return "覆盖后"; } } public class Class3 { public void test() { Class1 a = new Class2(); string z = a.test1(); //此时Z 重写后 Class2将test1重写了。 string zz = a.test2(); //此时ZZ 覆盖前 } }
5、单例模式
一、使用类行为创建
单例模式的类中,需要注意的事项:
(1)类变量 _singleTon需要被private static修饰,用于获取对象的方法(static类型)以及防止类的外部访问。
(2)获取类对象的方法 getInstance() 需要被public static修饰,外部通过类名加 . 的方式获取对象。
总结:类中含有两种东西(对象变量_singleTon,获取对象的方法getInstance),其中第一个是private static,后一个public static。
/// <summary> /// 单例模式 /// </summary> public class SingleTon { private static SingleTon _singleTon = null;public static SingleTon getInstance() { if (_singleTon == null) { _singleTon = new SingleTon(); } return _singleTon; } }
此时有一个缺点,多线程情况下(两个线程同时获取类对象,就会创建两个对象)
优化一下(加上同步锁)
/// <summary> /// 单例模式 /// </summary> public class SingleTon { private static SingleTon _singleTon = null; private static object SingleTon_lock = new object(); //同步锁 /// <summary> /// 防止类的外部使用new的方式创建该对象 /// </summary> public static SingleTon getInstance() { lock (SingleTon_lock) { if (_singleTon == null) { _singleTon = new SingleTon(); } return _singleTon; } } }
二、使用静态变量创建
public class SingletonThird { /// <summary> /// 静态变量 /// </summary> private static SingletonThird _SingletonThird = new SingletonThird(); public static SingletonThird CreateInstance() { return _SingletonThird; } }
是不是觉得很优雅, 利用静态变量去实现单例, 由CLR保证,在程序第一次使用该类之前被调用,而且只调用一次
PS: 但是他的缺点也很明显, 在程序初始化后, 静态对象就被CLR构造了, 哪怕你没用。
三、使用静态构造函数创建
public class SingletonSecond { private static SingletonSecond _SingletonSecond = null; static SingletonSecond() { _SingletonSecond = new SingletonSecond(); } public static SingletonSecond CreateInstance() { return _SingletonSecond; } }
静态构造函数:只能有一个,无参数的,程序无法调用 。
同样是由CLR保证,在程序第一次使用该类之前被调用,而且只调用一次
同静态变量一样, 它会随着程序运行, 就被实例化, 同静态变量一个道理。
(1). 值类型的数据存储在内存的栈中;引用类型的数据存储在内存的堆中,而内存单元中只存放堆中对象的地址。
(2). 值类型存取速度快,引用类型存取速度慢。
(3). 值类型表示实际数据,引用类型表示指向存储在内存堆中的数据的指针或引用
(4). 值类型继承自System.ValueType,引用类型继承自System.Object
(5). 栈的内存分配是自动释放;而堆在.NET中会有GC来释放
(6). 值类型的变量直接存放实际的数据,而引用类型的变量存放的则是数据的地址,即对象的引用。
(7). 值类型变量直接把变量的值保存在堆栈中,引用类型的变量把实际数据的地址保存在堆栈中,而实际数据则保存在堆中。注意,堆和堆栈是两个不同的概念,在内存中的存储位置也不相同,堆一般用于存储可变长度的数据,如字符串类型;而堆栈则用于存储固定长度的数据,如整型类型的数据int(每个int变量占用四个字节)。由数据存储的位置可以得知,当把一个值变量赋给另一个值变量时,会在堆栈中保存两个完全相同的值;而把一个引用变量赋给另一个引用变量,则会在堆栈中保存对同一个堆位置的两个引用,即在堆栈保存的是同一个堆的地址。在进行数据操作时,对于值类型,由于每个变量都有自己的值,因此对一个变量的操作不会影响到其它变量;对于引用类型的变量,对一个变量的数据进行操作就是对这个变量在堆中的数据进行操作,如果两个引用类型的变量引用同一个对象,实际含义就是它们在堆栈中保存的堆的地址相同,因此对一个变量的操作就会影响到引用同一个对象的另一个变量。
int a = 1; int b = a; a = 3; //此时b仍然为1 Class2 cl = new Class2(); Class2 c2 = cl; cl.a = "123456"; //此时引用C2的a也变成了123456
7、try catch finally 异常
finally里面的语句,无论程序是否出现异常或者return,都会执行finally里面的语句。
使用场景(关闭数据库连接,无论是否执行成功,都要关闭了数据库连接)
使用方法 try与catch(可以有多个,也可以没有,没有时需要加finally,finally只能有一个或者没有)。
try { //执行的代码,其中可能有异常。一旦发现异常,则立即跳到catch执行。否则不会执行catch里面的内容 } catch { //除非try里面执行代码发生了异常,否则这里的代码不会执行 } finally { //不管什么情况都会执行,包括try catch 里面用了return ,可以理解为只要执行了try或者catch,就一定会执行 finally }
8、索引器
索引器由 修饰符 返回类型 加 this[参数类型 参数值](public string this[int index,string b]{ }) 构成, 索引器的索引值不受类型限制。
索引器里面需要有get set访问器。
调用方式可以是:
Class cl=new Class();
cl[0]="对class对象里面a赋值";
索引器的作用: 可以使得类的实例能够像数组那样使用一样,又称为带参属性
(1)索引器与数组的比较:
索引器的索引值不受类型限制。用来访问数组的索引值一定是整数,而索引器可以是其他类型的索引值。
索引器允许重载,一个类可以有多个索引器。
索引器不是一个变量没有直接对应的数据存储地方。索引器有get和set访问器。
索引器允许类和结构的实例按照与数组相同的方式进行索引,索引器类似与属性,不同之处在于他们的访问器采用参数。被称为有参属性。
(2)索引器与属性的比较:
标示方式:属性以名称来标识,索引器以函数签名来标识。
索引器可以被重载。属性则不可以被重载。
属性可以为静态的,索引器属于实例成员,不能被声明为static
public string a { get; set; } public string b { get; set; } public string this[int index] { get { if (index == 0) { return a; } else return b; } set { if (index == 0) { a = value; } else b = value; } }