自己用的C#基础学习笔记(二)——C#面向对象(3)

来源:互联网 发布:怎么搭建python环境 编辑:程序博客网 时间:2024/05/23 02:00

第十四天

1.1 零碎知识

1.1.1 访问修饰符

Public 公共的

Private 私有的

Protected 受保护的,子类和当前类的内部可以使用

Internal 在项目中使用,在同一个项目中与public的访问权限一样。不同命名空间下不能访问

不写修饰符时,默认是internal。修饰类的访问修饰符只能是publicinternal

Protectedinternal的访问权限比较:

在同一项目中,internal的权限高于protected。跨项目时,后者高于前者。

修饰virtual时不能用private,因为虚方法要实现

重写父类虚方法时,访问修饰符要一致

1.1.2 readonly常量

可以在类里直接初始化,还可以在构造函数中直接初始化

const的比较:

Const是静态常量,在编译期(写代码时期)就进行解析,并将初始化的值替换成常量值。

Readonly是动态常量,它的值是在运行时候才会获取。编译期间会将其作为只读常量,程序启动运行时才会赋值。

举例:

Class student{

Public readonly int age;

Public student(int a){

This.age = a;

}

也可以是对象

Public readonly student ss;

Public student(student ss){

This.student = ss;

}}

1.1.3 静态构造函数

静态类的静态构造函数,只能在静态类中。

使用静态类的任意一个成员时候,系统会自动调用(先于要调用的成员),只会调用一次

static Person(){//不允许出现访问修饰符,必须没有参数,不能重载,只有一个。

Cw(“jingtaigouzao”);

}

1.1.4 密封类和部分类

密封类:能继承别的类,别的类不能继承密封类,只能做子类。

sealed是密封类的关键字

举例:public sealed class Person{}

部分类:将一个类分成两个部分(必须在同一命名空间),两部分的成员通用。

关键字partial

Public partial class student{第一部分

Private int a;

}

Public partial class student{第二部分

Public void ddd(){

a= 3;

}

}

1.2 栈和队列

1.2.1 

栈:一种存储数据的线性表,栈是限定仅能在表尾插入和删除

特点:先进后出,后进先出,第一个进来的在最后,叫栈底元素。最后一个进栈的元素叫栈顶元素。

栈顶指针top,永远在栈顶元素的上面

Stack pa = new Stack();

Pa.push(任意类型object);压栈,入栈pa.push(3);

Object n = Pop();出栈

Object n = Peek()取栈顶元素,元素并没有出栈,只是通过指针查看。

可以用foreach遍历栈

Foreach(var I in pa){

}

Clear();清除所有元素

1.2.2 队列

一种线性表,存储数据用的。

表头删除,表尾插入,先进先出,后进后出

第一个入队的叫队头元素,最后一个叫队尾元素

Queue qq = new Queue();

Object n = qq.Dequeue();出队(删除元素)

Enqueue();入队

Object n = qq.Peek();获取队头元素,但不出队。

Clear();清除队列

第十五天

2.1 委托

可以理解为一个方法

委托也称回调函数。

可以在类下定义,也可以在命名空间下定义

关键字delegate

举例:public delegate void mydele(string str);定义了一个委托(即一个函数的类型)

委托的签名和代表函数的签名要一致

Public void test(string a,mydele del){把函数当做参数传给参数,函数作为参数就是委托

Del(a);相当于调用了某个函数。

}

此时,只要将函数名称作为参数传递给del就可以直接调用对应的方法

主函数中调用委托:

Mydele del = new mydele(SayHrllo);

Del();

也可以:mydele del = SayHello;

2.2 匿名函数

public static void Main (string[] args){//匿名函数:给委托绑定方法时,这个方法只使用一次,我们可以考虑把这个方法写成一个匿名方法。
     string[] nums = new string[]{"dsad","dsda","fdgs"};
     GetString(nums,delegate(string str){str.ToLower();});//此处就是匿名函数,其实就是没有函数名,不用定义,直接使用delegate定义函数体
}
static void GetString(string[] str,del del){
    for (int i = 0; i < str.Length; i++) {
       del (str[i]);
    }
}

2.2.1 泛型委托匿名使用

主函数内容

GetMax<int>(new int[]{2,34,45,4,56,5},delegate(int a,int b){ return a-b;  });

GetMax<string>(new string[]{"2sdf","3ff4","sdf45","dsf4","5fsd6"},delegate(string a,string b){ return a.Length-b.Length;  });

定义函数

public delegate int dele<T>(T a,T b);
static T GetMax<T>(T[] nums,dele<T> del){
            T max = nums[0];
            for (int i = 0; i < nums.Length; i++) {
                if (del (nums [i], max) > 0) {
                    max = nums [i];
                }
            }
            return max;
}

 

2.2.2 lamda表达式

dele d1 = (参数) =>{方法体};

例如:dele d = (string str)=>{cw(“fsdafsf”);}

Lamda表达式就是为了方便定义匿名方法

2.3 多播委托

通过+=给委托增加注册的方法

通过-=给委托取消方法的注册

= 会把当前的值覆盖掉。

委托可以=null

例如:

Dele del = M1;  m1是方法名

Del+=M2;

Del+=M3;

Del-=M1;

Del();

2.4 事件

发生一件事情的时候,所触发的这个事情就叫做事件。

定义:public event 委托名 事件名

事件只能依存于委托,所以注册方式和委托一样,而且只能在内部调用,不可以在外部调用(相当于只能在类内使用),并且不能赋值为null

事件本质上来说就是一种委托。

注册事件用+=,取消事件用减等。

举例:

Public delegate void dele();

Public class Test{

Public event del sun;//事件

Public void diaoyong(){//只能通过方法在内部调用

Sun();

}

Public vid Thing(){

Console.wrireLine(“下雨了”);

}

Public vid Thing1(){

Console.wrireLine(“没带伞”);

}

}

主函数:

Test t = new Test();

t.sun+=t.Thing;

t.sun+=t.Thing1;

t.sun();错误,无法在外部调用

2.5 索引器

索引器就是类或者结构体的实例通过与数组相同的方式去索引类中的内容,类似于属性(也可以说是一种特殊的属性)。

举例:

public class Teat {

        public int age1;

        public int age2;

        public int age3;

        public int this[int index] {//索引器,没有名字,用this关键字

            get {

                switch (index) {

                    case 0:

                        return age1;

                    case 1:

                        return age2;

                    case 2:

                        return age3;

                    default:

                        return 0;

                }

            }

            set {

                switch (index) {

                    case 0:

                        age1 = value;

                        break;

                    case 1:

                        age2 = value;

                        break;

                    case 2:

                        age3 = value;

                        break;

                    default:

                        age1 = 0;

                        break;

                }

            }

        }

}

主函数:

Teat t= new Teat();

T[0] = 3; T[1] = 13;

T[2] = 23;

 

 

 

 

C#线程以及线程锁(网上摘录)

      1.几种同步方法的区别

      lockMonitor.NET用一个特殊结构实现的,Monitor对象是完全托管的、完全可移植的,并且在操作系统资源要求方 面可能更为有效,同步速度较快,但不能跨进程同步。lockMonitor.EnterMonitor.Exit方法的封装),主要作用是锁定临界区,使临 界区代码只能被获得锁的线程执行。Monitor.WaitMonitor.Pulse用于线程同步,类似信号操作,个人感觉使用比较复杂,容易造成死 锁。

      互斥体Mutex和事件对象EventWaitHandler属于内核对象,利用内核对象进行线程同步,线程必须要在用户模式和内核模 式间切换,所以一般效率很低,但利用互斥对象和事件对象这样的内核对象,可以在多个进程中的各个线程间进行同步。

      互斥体Mutex类似于一个接力棒,拿到接力棒的线程才可以开始跑,当然接力棒一次只属于一个线程(Thread Affinity),如果这个线程不释放接力棒(Mutex.ReleaseMutex),那么没办法,其他所有需要接力棒运行的线程都知道能等着看热 闹。

      EventWaitHandle 类允许线程通过发信号互相通信。 通常,一个或多个线程在EventWaitHandle 上阻止,直到一个未阻止的线程调用Set 方法,以释放一个或多个被阻止的线程。

      2.什么时候需要锁定

      首先要理解锁定是解决竞争条件的,也就是多个线程同时访问某个资源,造成意想不到的结果。比如,最简单的情况是,一个计数器,两个线程 同时加一,后果就是损失了一个计数,但相当频繁的锁定又可能带来性能上的消耗,还有最可怕的情况死锁。那么什么情况下我们需要使用锁,什么情况下不需要 呢?

      1)只有共享资源才需要锁定
      只有可以被多线程访问的共享资源才需要考虑锁定,比如静态变量,再比如某些缓存中的值,而属于线程内部的变量不需要锁定。 

      2)多使用lock,少用Mutex
      如果你一定要使用锁定,请尽量不要使用内核模块的锁定机制,比如.NETMutexSemaphoreAutoResetEventManuResetEvent,使用这样的机制涉及到了系统在用户模式和内核模式间的切换,性能差很多,但是他们的优点是可以跨进程同步线程,所以应该清 楚的了解到他们的不同和适用范围。

      3)了解你的程序是怎么运行的
      实际上在web开发中大多数逻辑都是在单个线程中展开的,一个请求都会在一个单独的线程中处理,其中的大部分变量都是属于这个线程的,根本没有必要考虑锁 定,当然对于ASP.NET中的Application对象中的数据,我们就要考虑加锁了。

      4)把锁定交给数据库
      数 据库除了存储数据之外,还有一个重要的用途就是同步,数据库本身用了一套复杂的机制来保证数据的可靠和一致性,这就为我们节省了很多的精力。保证了数据源 头上的同步,我们多数的精力就可以集中在缓存等其他一些资源的同步访问上了。通常,只有涉及到多个线程修改数据库中同一条记录时,我们才考虑加锁。 

      5)业务逻辑对事务和线程安全的要求
      这 条是最根本的东西,开发完全线程安全的程序是件很费时费力的事情,在电子商务等涉及金融系统的案例中,许多逻辑都必须严格的线程安全,所以我们不得不牺牲 一些性能,和很多的开发时间来做这方面的工作。而一般的应用中,许多情况下虽然程序有竞争的危险,我们还是可以不使用锁定,比如有的时候计数器少一多一, 对结果无伤大雅的情况下,我们就可以不用去管它。

      3.InterLocked类

      Interlocked 类提供了同步对多个线程共享的变量的访问的方法。如果该变量位于共享内存中,则不同进程的线程就可以使用该机制。互锁操作是原子的,即整个操作是不能由相 同变量上的另一个互锁操作所中断的单元。这在抢先多线程操作系统中是很重要的,在这样的操作系统中,线程可以在从某个内存地址加载值之后但是在有机会更改 和存储该值之前被挂起。

      我们来看一个InterLock.Increment()的例子,该方法以原子的形式递增指定变量并存储结果,示例如下:
    class InterLockedTest
    {
        public static Int64 i = 0;

        public static void Add()
        {
            for (int i = 0; i < 100000000; i++)
            {
                Interlocked.Increment(ref InterLockedTest.i);
                //InterLockedTest.i = InterLockedTest.i + 1;
            }
        }


        public static void Main(string[] args)
        {
            Thread t1 = new Thread(new ThreadStart(InterLockedTest.Add));
            Thread t2 = new Thread(new ThreadStart(InterLockedTest.Add));
            t1.Start();
            t2.Start();
            t1.Join();
            t2.Join();
            Console.WriteLine(InterLockedTest.i.ToString());
            Console.Read();
        }
    }

      输出结果200000000,如果InterLockedTest.Add()方法中用注释掉的语句代替Interlocked.Increment()方法,结果将不可预知,每次执行结果不同。InterLockedTest.Add()方法保证了加1操作的原子性,功能上相当于自动给加操作使用了lock锁。同时我们也注意到InterLockedTest.Add()用时比直接用+号加1要耗时的多,所以说加锁资源损耗还是很明显的。

      另外InterLockedTest类还有几个常用方法,具体用法可以参考MSDN上的介绍。

   4.集合类的同步

      .NET在一些集合类,比如QueueArrayListHashTableStack,已经提供了一个供lock使用的对象SyncRoot。用Reflector查看了SyncRoot属性(Stack.SynchRoot略有不同)的源码如下:
public virtual object SyncRoot
{
    get
    {
        if (this._syncRoot == null)
        {
            //如果_syncRootnull相等,将new object赋值给_syncRoot
            //Interlocked.CompareExchange方法保证多个线程在使用syncRoot时是线程安全的
            Interlocked.CompareExchange(ref this._syncRoot, new object(), null);
        }
        return this._syncRoot;
    }
}