c# flag 实例

2013-06-10 12:22:49 norsd 阅读数 5156


[Flags]
enum eTest
{
OK = 0 ,
Error1 = 1,
Error2 = 2,
Error3 = 4,
}



在以上的Enum中 , 我们在判断一个 eTest 实例是否有错误 Error1 可以使用 HasFlag(eTest.Error1) 但是如果要判断 是否 OK 


不能使用 HasFlag(eTest.OK) ,因为这样始终返回 True  , 而要用  eTestInstance == eTest.OK 这样的语句判别

2017-03-22 17:09:01 huangxinchen520 阅读数 235
说到FlagsAttribute,源自前几天看到了一小段代码,大概意思就是根据航班政策来返回哪些配送方式是否可用,根据这些是否可用

来隐藏或者开启界面的相关配送方式,如果大家订过机票可能知道配送方式有很多种,比如“无需打印行程单(PJN)”,“机场自取(Airport)”,

“市内配送(CND)”,“快递(EMS)”等等。

根据上面的逻辑就可以知道,配送方式是有两种状态,可用与不可用,在逻辑实现上,很容易就想到了bit位,每一个位代表一个配送方式,

0表示不可用,1表示可用,所以航班接口只要给一个数字就行了,我只需要判断bit位中哪些是1就行了。

比如用8位byte字段为例:

从图中可以看到,快递(EMS)是不可用的,那么怎么判断呢,其实也就17&32就ok了,如果为32,则说明可用,为0则不可用,其他

的判断可以用同样的手段。

上次看到的代码逻辑就这样了,不过毕竟在一个团队里面,水平参差不齐,用纯数字来&,|,^,最起码不是那么容易理解的,如果

配上枚举的话,可能就更完美了。

    说起枚举,其实就是编译器给我们的语法糖,本质上来说就是一个继承在Enum类型下的一个个const字段,既然是const,那

就天生具备(+,-,* ,/ ^,| &)这样常规的数学运算。

举个例子:

 1     [Flags]
 2     enum Deliver : byte
 3     {
 4         CND = 0x01,
 5         PJS = 0x02,
 6         SND = 0x04,
 7         PJN = 0x08,
 8         Airport = 0x16,
 9         EMS = 0x32
10     }

 

然后看看上面的Enum生成的IL代码。

 

 

可能有人会问,这里的“uint8” 是怎么回事,其实这个就是隐藏到枚举类型后面的真实的基元类型,可以用GetUnderlyingType来获取。

 

通常情况下,枚举只能显示一个状态,那么如果让枚举显示多个状态,这个时候就可以用FlagAttribute来标记,让标志位来处理枚举,

来做强大的组合功能。

举个例子:从图中航班政策返回的17的数字来看,我们知道Airport和CND是可用的,如果用了FlagAttribute标记后,这次我们不用担

心,直接将17转化为枚举即可。

从图中看到,经过枚举转换后,可能对程序员的理解以及记录log上面,更方便分析和追踪。

 

 

2019-07-08 08:05:54 weixin_44543135 阅读数 129

C#接口的简单实例

C#中的接口和抽象类类似,继承接口的任何非抽象类型都必须实现接口的所有成员,也就是通过接口可以实现相同接口的引用来访问来实现相同接口的不同类的方法;其实是使用虚方法通过相同的的引用调用相同基础的不同类;我们可以在接口中添加一些元素;
注意:1、接口中不能包含字段,如变量,否则编译将会报错;
2、接口中的成员不能有定义,例如不能写方法体;
3、接口是类的实现规范,也就是说接口规定了方法的原型并有类实现接口所定义的原型,通过类继承接口来调用接口定义方法原型
4、调用接口的类必须实现接所调用接口里所定义的方法原型
5、类调用接口的方法原型必须有足够的访问权限(public)
6、接口可以实现接口调用相同的接口不同的类
7、一个类可以实现多个接口(类:接口1,接口2{})
8、接口一旦被实现,实现类必须实现接口中的所有成员,除非实现类本身是抽象类。
9、C#是单继承,接口是解决C#里面类可以同时继承多个基类的问题。
10、不能直接实例化接口
接口和抽象类的区别:
1、 接口用于规范,抽象化用于共性;抽象类是类智能被单继承,而接口可以实现多个继承。
2、 接口只能声明方法、属性、事件、索引器,而抽象类可以有方法的实现,也可以定义非静态类的变量
下面是通过实现接口方法的一个简单形象的实例,当人们面前站着两个人,分别是中国人和美国人,如果想和这两人交流,那么必须对中国人说汉语,对美国人说英语。因此在程序中可以创建一个接口,该接口定义一个方法用于对话,而对话这个方法是在类中实现的。分别创建一个中国人类和一个美国人类,这两个类都继承自接口,在中国人类中说汉语,在美国人类中说英语,当和不同人交流时,实例化接口,并调用类中的方法即可。代码如下;
(1) 首先先声明一个接口;

/// <summary>
/// 声明一个接口,用于定义Seak方法,而具体Speak方法功能的实现是在类中进行的        /// </summary>
  interface ISelectLanguage
       {
            //方法
            void Speak(string str);
       }

(2)创建两个类来实现接口

        /// <summary>
        /// 如果跟中国人对话,则说汉语
        /// </summary>
        class C_SpeakChinese : ISelectLanguage
        {
            //实现接口方法(重写方法体)
            public void Speak(string str)
            {
                MessageBox.Show("您对中国友人说:" + str, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }
        /// <summary>
        /// 如果跟美国人对话,则说英语
        /// </summary>
        class C_SpeakEnglish : ISelectLanguage
        {
             //实现接口方法(重写方法体)
            public void Speak(string str)
            {
                MessageBox.Show("您对美国友人说:" +str, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }

(3)定义方法触发接口的方法

 public bool CheckChinese(string str)
        {
            bool flag = false;
            UnicodeEncoding a = new UnicodeEncoding();
            byte[] b = a.GetBytes(str);
            for(int i=0;i<b.Length;i++)
            {
                i++;
                if (b[i] != 0)
                {
                    flag = true;
                }
                else
                {
                    flag = false;
                }
            }
            return flag;
        }
//点击事件
        private void button1_Click(object sender, EventArgs e)
        {
            if (txtContent.Text == "")
            {
                MessageBox.Show("不想跟友人说点什么吗?", "警告", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            else
            {
                //判断下拉框是选中值来执行相应的操作
                if (comboBox1.SelectedIndex == 0)//与中国人对话
                {
                    if (CheckChinese(txtContent.Text))
                    {
                        ISelectLanguage Interface1 = new C_SpeakChinese();
                        Interface1.Speak(txtContent.Text);
                    }
                    else
                    {
                        MessageBox.Show("请和中国友人说汉语?", "警告", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        return;
                    }
                }
                else//与美国人对话
                {
                    if (CheckChinese(txtContent.Text))
                    {
                        MessageBox.Show("请和美国友人说英语?", "警告", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        return;
                    }
                    else
                    {
                        ISelectLanguage Interface1 = new C_SpeakEnglish();
                        Interface1.Speak(txtContent.Text);
                    }
                }
            }
        }

窗体显示结果;
1、 C_SpeakChinese 实现的接口的方法,输入中文字符串的结果,英文反之
在这里插入图片描述
2、 C_SpeakEnglish 实现接口的方法,输入中文字符串;英文反之
在这里插入图片描述

2007-09-01 10:49:00 wlwqw 阅读数 818

 

单个写入程序/多个阅读程序在.Net类库中其实已经提供了实现,即System.Threading.ReaderWriterLock类。本文通过对常见的单个写入/多个阅读程序的分析来探索c#的多线程编程。

问题的提出

   所谓单个写入程序/多个阅读程序的线程同步问题,是指任意数量的线程访问共享资源时,写入程序(线程)需要修改共享资源,而阅读程序(线程)需要读取数据。在这个同步问题中,很容易得到下面二个要求:

   1) 当一个线程正在写入数据时,其他线程不能写,也不能读。

   2) 当一个线程正在读入数据时,其他线程不能写,但能够读。

   在数据库应用程序环境中经常遇到这样的问题。比如说,有n个最终用户,他们都要同时访问同一个数据库。其中有m个用户要将数据存入数据库,n-m个用户要读取数据库中的记录。

   很显然,在这个环境中,我们不能让两个或两个以上的用户同时更新同一条记录,如果两个或两个以上的用户都试图同时修改同一记录,那么该记录中的信息就会被破坏。

   我们也不让一个用户更新数据库记录的同时,让另一用户读取记录的内容。因为读取的记录很有可能同时包含了更新和没有更新的信息,也就是说这条记录是无效的记录。

实现分析

   规定任一线程要对资源进行写或读操作前必须申请锁。根据操作的不同,分为阅读锁和写入锁,操作完成之后应释放相应的锁。将单个写入程序/多个阅读程序的要求改变一下,可以得到如下的形式:

   一个线程申请阅读锁的成功条件是:当前没有活动的写入线程。

   一个线程申请写入锁的成功条件是:当前没有任何活动(对锁而言)的线程。

   因此,为了标志是否有活动的线程,以及是写入还是阅读线程,引入一个变量m_nActive,如果m_nActive > 0,则表示当前活动阅读线程的数目,如果m_nActive=0,则表示没有任何活动线程,m_nActive <0,表示当前有写入线程在活动,注意m_nActive<0,时只能取-1的值,因为只允许有一个写入线程活动。

   为了判断当前活动线程拥有的锁的类型,我们采用了线程局部存储技术(请参阅其它参考书籍),将线程与特殊标志位关联起来。

   申请阅读锁的函数原型为:public void AcquireReaderLock( int millisecondsTimeout ),其中的参数为线程等待调度的时间。函数定义如下:

public void AcquireReaderLock( int millisecondsTimeout )

{

// m_mutext很快可以得到,以便进入临界区

m_mutex.WaitOne( );

// 是否有写入线程存在

bool bExistingWriter = ( m_nActive < 0 );

if( bExistingWriter )

{ //等待阅读线程数目加1,当有锁释放时,根据此数目来调度线程

m_nWaitingReaders++;

}

else

{ //当前活动线程加1

m_nActive++;

}

m_mutex.ReleaseMutex();

//存储锁标志为Reader

System.LocalDataStoreSlot slot = Thread.GetNamedDataSlot(m_strThreadSlotName);

object obj = Thread.GetData( slot );

LockFlags flag = LockFlags.None;

if( obj != null )

flag = (LockFlags)obj ;

if( flag == LockFlags.None )

{

Thread.SetData( slot, LockFlags.Reader );

}

else

{

Thread.SetData( slot, (LockFlags)((int)flag | (int)LockFlags.Reader ) );

}


if( bExistingWriter )

{ //等待指定的时间

this.m_aeReaders.WaitOne( millisecondsTimeout, true );

}

}


   它首先进入临界区(用以在多线程环境下保证活动线程数目的操作的正确性)判断当前活动线程的数目,如果有写线程(m_nActive<0)存在,则等待指定的时间并且等待的阅读线程数目加1。如果当前活动线程是读线程(m_nActive>=0),则可以让读线程继续运行。

   申请写入锁的函数原型为:public void AcquireWriterLock( int millisecondsTimeout ),其中的参数为等待调度的时间。函数定义如下:

public void AcquireWriterLock( int millisecondsTimeout )

{

// m_mutext很快可以得到,以便进入临界区

m_mutex.WaitOne( );

// 是否有活动线程存在

bool bNoActive = m_nActive == 0;

if( !bNoActive )

{

m_nWaitingWriters++;

}

else

{

m_nActive--;

}

m_mutex.ReleaseMutex();

//存储线程锁标志

System.LocalDataStoreSlot slot = Thread.GetNamedDataSlot( "myReaderWriterLockDataSlot" );

object obj = Thread.GetData( slot );

LockFlags flag = LockFlags.None;

if( obj != null )

flag = (LockFlags)Thread.GetData( slot );

if( flag == LockFlags.None )

{

Thread.SetData( slot, LockFlags.Writer );

}

else

{

Thread.SetData( slot, (LockFlags)((int)flag | (int)LockFlags.Writer ) );

}

//如果有活动线程,等待指定的时间

if( !bNoActive )

this.m_aeWriters.WaitOne( millisecondsTimeout, true );

}


   它首先进入临界区判断当前活动线程的数目,如果当前有活动线程存在,不管是写线程还是读线程(m_nActive),线程将等待指定的时间并且等待的写入线程数目加1,否则线程拥有写的权限。

   释放阅读锁的函数原型为:public void ReleaseReaderLock()。函数定义如下:

public void ReleaseReaderLock()

{

System.LocalDataStoreSlot slot = Thread.GetNamedDataSlot(m_strThreadSlotName );

LockFlags flag = (LockFlags)Thread.GetData( slot );

if( flag == LockFlags.None )

{
return;

}

bool bReader = true;

switch( flag )

{

case LockFlags.None:

break;

case LockFlags.Writer:

bReader = false;

break;

}

if( !bReader )

return;

Thread.SetData( slot, LockFlags.None );

m_mutex.WaitOne();

AutoResetEvent autoresetevent = null;

this.m_nActive --;

if( this.m_nActive == 0 )

{

if( this.m_nWaitingReaders > 0 )

{

m_nActive ++ ;

m_nWaitingReaders --;

autoresetevent = this.m_aeReaders;

}

else if( this.m_nWaitingWriters > 0)

{

m_nWaitingWriters--;

m_nActive --;

autoresetevent = this.m_aeWriters ;

}

}

m_mutex.ReleaseMutex();

if( autoresetevent != null )

autoresetevent.Set();

}


   释放阅读锁时,首先判断当前线程是否拥有阅读锁(通过线程局部存储的标志),然后判断是否有等待的阅读线程,如果有,先将当前活动线程加1,等待阅读线程数目减1,然后置事件为有信号。如果没有等待的阅读线程,判断是否有等待的写入线程,如果有则活动线程数目减1,等待的写入线程数目减1。释放写入锁与释放阅读锁的过程基本一致,可以参看源代码。

   注意在程序中,释放锁时,只会唤醒一个阅读程序,这是因为使用AutoResetEvent的原历,读者可自行将其改成ManualResetEvent,同时唤醒多个阅读程序,此时应令m_nActive等于整个等待的阅读线程数目。

测试

   测试程序取自.Net FrameSDK中的一个例子,只是稍做修改。测试程序如下,

using System;

using System.Threading;

using MyThreading;

class Resource {

myReaderWriterLock rwl = new myReaderWriterLock();

public void Read(Int32 threadNum) {

rwl.AcquireReaderLock(Timeout.Infinite);

try {

Console.WriteLine("Start Resource reading (Thread={0})", threadNum);

Thread.Sleep(250);

Console.WriteLine("Stop Resource reading (Thread={0})", threadNum);

}

finally {

rwl.ReleaseReaderLock();

}

}

public void Write(Int32 threadNum) {

rwl.AcquireWriterLock(Timeout.Infinite);

try {

Console.WriteLine("Start Resource writing (Thread={0})", threadNum);

Thread.Sleep(750);

Console.WriteLine("Stop Resource writing (Thread={0})", threadNum);

}

finally {

rwl.ReleaseWriterLock();

}

}

}

class App {

static Int32 numAsyncOps = 20;

static AutoResetEvent asyncOpsAreDone = new AutoResetEvent(false);

static Resource res = new Resource();



public static void Main() {

for (Int32 threadNum = 0; threadNum < 20; threadNum++) {

ThreadPool.QueueUserWorkItem(new WaitCallback(UpdateResource), threadNum);

}

asyncOpsAreDone.WaitOne();

Console.WriteLine("All operations have completed.");

Console.ReadLine();

}

// The callback method's signature MUST match that of a System.Threading.TimerCallback

// delegate (it takes an Object parameter and returns void)

static void UpdateResource(Object state) {

Int32 threadNum = (Int32) state;

if ((threadNum % 2) != 0) res.Read(threadNum);

else res.Write(threadNum);

if (Interlocked.Decrement(ref numAsyncOps) == 0)

asyncOpsAreDone.Set();

}

}


   从测试结果中可以看出,可以满足单个写入程序/多个阅读程序的实现要求。
2019-01-04 08:47:21 lzf601 阅读数 1716

`C#通过ADB给手机发送命令


    // ----------------------------------------- Adb.exe path, leave blank if in same directory as app or included in PATH
    private string adbPath = "adb";
    public string AdbPath
    {
        get { return adbPath; }
        set
        {
            if (File.Exists(value)) adbPath = value;
            else adbPath = "\"" + adbPath + "\"";
        }
    }

    // ----------------------------------------- Adb command timeout - usable in push and pull to avoid hanging while executing
    private int adbTimeout;//默认执行每一次操作至少5秒才会返回Output结果,也就意味着5秒之后才可以进行新的adb命令
    public int AdbTimeout//初始时赋值及时5000=5秒//为0时返回5000
    {
        get { return adbTimeout > 0 ? adbTimeout : 5000; }
        set { adbTimeout = value; }
    }









    //↓↓↓↓↓↓↓↓↓↓↓↓↓核心就是完成一个 带有等待过程的 SendCommand异步方法,最终返回更新的Output↓↓↓↓↓↓↓↓
    // ----------------------------------------- Create our emulated shell here and assign events
    // Create a background thread an assign work event to our emulated shell method
    //(这句话没用,后面根本不是用它)private Process Shell;//在程序开发中,一个程序经常需要去调用其他的程序,C#中Process类正好提供了这样的功能。它提供对本地和远程进程的访问并使您能够启动和停止本地系统进程
    // Needed data types for our emulated shell
    string Command = "";
    bool Complete = false;
    //①声明一个BackgroundWorker
    BackgroundWorker CMD;//❤❤何甜加= new BackgroundWorker();//也是一个可视控件,势必thread简单的多的后台线程
    // Send a command to emulated shell
    public void SendCommand(string command)
    {
        //②初始化BackgroundWorker,设置属性(并提前在构造函数里绑定事件)
        CMD= new BackgroundWorker();//❤❤何甜加
        CMD.WorkerSupportsCancellation = true;//该线程支持异步取消
        Command = command;
        //③ SendCommand()调用时触发事件,异步执行CMD_Send方法→即将方法绑定给 发生在SendCommand处的 CMD.RunWorkerAsync()事件
        CMD.DoWork += new DoWorkEventHandler(CMD_Send);//❤❤何甜加
        CMD.RunWorkerAsync();//开始执行后台操作(无形中触发CMD.DoWork事件)
        //补课:
        //DoWork 调用 RunWorkerAsync 时发生
        //ProgressChanged 调用 ReportProgress 时发生(用显示进度条,这里没用)
        while (!Complete) Sleep(500);//可以加自定代码//一直等到CMD_Send()里把Complete为true,才表进程完毕或者失败
        Complete = false;//重置标志位
    }
    
    //④定义被绑定的方法// Create an emulated shell for executing commands
    private void CMD_Send(object sender, DoWorkEventArgs e)
    {
        ProcessStartInfo startInfo = new ProcessStartInfo()
        {
            WindowStyle = ProcessWindowStyle.Normal,//(何甜想看看)//ProcessWindowStyle.Hidden,
            CreateNoWindow = true,
            UseShellExecute = false,
            RedirectStandardOutput = true,
            FileName = "cmd.exe",
            Arguments = "/C \"" + Command + "\""
        };

        using (Process process = Process.Start(startInfo))//下方{}内运行完,此cmd外部exe自动释放清空
        {   //using在这里的用处(以前网上学过,和垃圾回收机制有关)
            //定义一个范围,在范围结束时处理对象。 
            //场景: 
            //当在某个代码段中使用了类的实例,而希望无论因为什么原因,只要离开了这个代码段就自动调用这个类实例的Dispose。 
            //要达到这样的目的,用try...catch来捕捉异常也是可以的,但用using也很方便。

            if (Command.StartsWith("\"" + adbPath + "\" logcat"))//若需要执行的Command命令行 是 写日志
            {
                Complete = true;
                process.WaitForExit();//等待关联进程退出(无限长24天直到退出:罕见)--自信不会卡死
                return;
            }

            if (!process.WaitForExit(AdbTimeout))//如果没有顺利结束的话(罕见情况)backgroundworker线程就会在这里会一直等==阻塞5秒,然后才下一句//等待关联进程退出的时间(以毫秒为单位)////如果关联进程已退出,则为 true;否则为 false
                process.Kill();//(罕见)

            Output = process.StandardOutput.ReadToEnd();//cmd返回的结果放在Output里保存着呢
            Complete = true;//无论成功失败,改变flag标志:Complete
        };
    }

    // Sleep until output
    public void Sleep(int milliseconds)
    {
        DateTime delayTime = DateTime.Now.AddMilliseconds(milliseconds);
        while (DateTime.Now < delayTime)
        {
            ;//可以填待做的事情Application.DoEvents();
        }
    }
    //↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑