• HslCommunication的OperateResult的使用细节说明,结果链操作示例。


    本篇博文主要说说hslcommunication的结果链的知识,说一下前因后果,以及目前最新的功能扩充,(V9.5.0以上)

    以前也写过一篇文章:https://www.cnblogs.com/dathlin/p/7865682.html 不看也没事,参考这篇新的文章就好了。

    首先还是聊聊,为什么会诞生这个 OperateResult ,比如我有个方法,获取一些信息的,或是执行一些操作的,比如读取文件的内容。

    		public string ReadFileContent( string path )
    		{
    			return System.IO.File.ReadAllText( path );
    		}
    

      很简单吧,方法里面复杂也没有关系的,如果这个方法保证不会发生异常,或是失败,那就没有关系,这样写也挺好的,但是事实就是极容易发生异常,就拿这个例子来说,可能因为文件不存在,可能因为其他异常。如果我们需要返回的内容包含下面三大块,肯定包括 1. 是否成功   2.错误消息   3.内容       于是我加了一个错误码,就有了下面的类(以下是简写)

    		public class OperateResult
                    {
    			public bool IsSuccess { get; set; }
    			public string Message { get; set; }
    			public int ErrorCode { get; set; }
    		}
    

      然后可能携带各种不同类型的结果内容,又可能是多个的,所以有了泛型的派生类,这算是泛型的一个经典的例子,另一个例子就是List<T>数组了。

    public class OperateResult<T> : OperateResult
    public class OperateResult<T1, T2> : OperateResult
    public class OperateResult<T1, T2, T3> : OperateResult
    public class OperateResult<T1, T2, T3, T4> : OperateResult
    public class OperateResult<T1, T2, T3, T4, T5> : OperateResult
    public class OperateResult<T1, T2, T3, T4, T5, T6> : OperateResult
    public class OperateResult<T1, T2, T3, T4, T5, T6, T7> : OperateResult
    public class OperateResult<T1, T2, T3, T4, T5, T6, T7, T8> : OperateResult
    public class OperateResult<T1, T2, T3, T4, T5, T6, T7, T8, T9> : OperateResult
    public class OperateResult<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> : OperateResult
    

      又定义了十个泛型类对象,最多可以携带10个不同类型的参数信息,当然,为了扩充一些转化信息,整个 OperateResult.cs 文件的源代码长达 3577 行源代码。

    好了,所以上面的方法可以改写为:

    		public OperateResult<string> ReadFileContent( string path )
    		{
    			try
    			{
    				return OperateResult.CreateSuccessResult( System.IO.File.ReadAllText( path ) );
    			}
    			catch(Exception ex)
    			{
    				return new OperateResult<string>( ex.Message );
    			}
    		}

      这样我们就能把结果信息返回了,当然了,实际可能更加复杂一点,比如下面所示,在读取文件之前,还需要检查当前账户是否有权限。

    		public bool CheckPermission( )
    		{
    			// 检查账户合法性,是否有权利下载
    			return true;
    		}
    
    		public OperateResult<string> ReadFileContent( string path )
    		{
    			if (!CheckPermission( )) return new OperateResult<string>( "当前无权读取文件的内容" );
    			try
    			{
    				return OperateResult.CreateSuccessResult( System.IO.File.ReadAllText( path ) );
    			}
    			catch(Exception ex)
    			{
    				return new OperateResult<string>( ex.Message );
    			}
    		}
    

      到这里,已经成型基本的意思了。我们再来说一下HslCommunication自身的经典应用,我们来看一个三菱PLC的数据读取示例,我们为了要读取一个地址的原始字节数据,会提供这样的方法,

    public override OperateResult<byte[]> Read( string address, ushort length )
    

      但是呢,实际上错误的原因是很多的,可能一开始地址输入错误了,可能网络发生了错误,可能PLC返回了一个错误码,然后进行解析得到正确的数据。那么底层这么实现

    		public override OperateResult<byte[]> Read( string address, ushort length )
    		{
    			// 获取指令
    			var command = BuildReadCommand(address, length, false, PLCNumber);
    			if (!command.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(command);
    
    			// 核心交互
    			var read = ReadFromCoreServer(command.Content);
    			if (!read.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(read);
    
    			// 错误代码验证
    			OperateResult check = CheckResponseLegal( read.Content );
    			if (!check.IsSuccess) return OperateResult.CreateFailedResult<byte[]>( check );
    
    			// 数据解析,需要传入是否使用位的参数
    			return ExtractActualData(read.Content, false);
    		}
    

      我们再来看看这个核心交互是怎么实现的?

    public OperateResult<byte[]> ReadFromCoreServer( byte[] send )
    		{
    			var result = new OperateResult<byte[]>( );
    			OperateResult<Socket> resultSocket = null;
    
    			InteractiveLock.Enter( );
    			try
    			{
    				// 获取有用的网络通道,如果没有,就建立新的连接
    				resultSocket = GetAvailableSocket( );
    				if (!resultSocket.IsSuccess)
    				{
    					IsSocketError = true;
    					AlienSession?.Offline( );
    					InteractiveLock.Leave( );
    					result.CopyErrorFromOther( resultSocket );
    					return result;
    				}
    
    				OperateResult<byte[]> read = ReadFromCoreServer( resultSocket.Content, send );
    
    				if (read.IsSuccess)
    				{
    					IsSocketError = false;
    					result.IsSuccess = read.IsSuccess;
    					result.Content = read.Content;
    					result.Message = StringResources.Language.SuccessText;
    				}
    				else
    				{
    					IsSocketError = true;
    					AlienSession?.Offline( );
    					result.CopyErrorFromOther( read );
    				}
    
    				ExtraAfterReadFromCoreServer( read );
    				InteractiveLock.Leave( );
    			}
    			catch
    			{
    				InteractiveLock.Leave( );
    				throw;
    			}
    
    			if (!isPersistentConn) resultSocket?.Content?.Close( );
    			return result;
    		}
    

      我们可以看到,一旦中间的某个环节发生了错误或是异常,这个错误信息会一直向上传递,直到传递给最上层的调用者。以此形成上下的链条。

    那么Convert,Check,Then是什么意思呢?主要是简化代码的。我们来看看下面的代码

    		public OperateResult<string> Write( )
    		{
    			OperateResult write = siemens.Write( "M100", (short)12 );
    			if (!write.IsSuccess) return OperateResult.CreateFailedResult<string>( write );
    
    			return OperateResult.CreateSuccessResult( "M100写入成功" );
    		}
    

      这个代码就可以简化为:

    		public OperateResult<string> Write( ) => siemens.Write( "M100", (short)12 ).Convert<string>( "M100写入成功" );
    

      我们看到代码简化了很多,所以Convert意思就是,如果原来的结果对象失败,就直接返回,如果成功,就返回给定的结果内容。

    我们再来看第二种情况:这种情况主要是对读取的内容进行一些判断操作。

    		public OperateResult Check( )
    		{
    			OperateResult<short> read = siemens.ReadInt16( "M100" );
    			if (!read.IsSuccess) return OperateResult.CreateFailedResult<string>( read );
    
    			if (read.Content == 10) return OperateResult.CreateSuccessResult( );
    			else return new OperateResult( "设备的数据值不对" );
    		}
    

      这个代码可以简化为:

    		public OperateResult Check( ) => siemens.ReadInt16( "M100" ).Check( m => m == 10, "设备的数据值不对" );
    

      当然,如果我的检查的方法比较复杂,也可以这么写:

    		public OperateResult CheckStatus(short value )
    		{
    			if (value == 1) return new OperateResult( "错误原因1" );
    			if (value == 2) return new OperateResult( "错误原因2" );
    			if (value == 3) return new OperateResult( "错误原因3" );
    			if (value == 4) return new OperateResult( "错误原因4" );
    			return OperateResult.CreateSuccessResult( );
    		}
    
    
    		public OperateResult Check( ) => siemens.ReadInt16( "M100" ).Check( m => CheckStatus( m ) );
    

      

    我们再来看看一种更复杂的情况。

    		public OperateResult StartPLC( )
    		{
    			// 这是一个启动PLC的方法,逻辑就是,M100.0是启动PLC,但是在启动之前,需要向PLC的多个地址写入初始参数。
    			OperateResult write = siemens.Write( "M200", (short)123 );
    			if (!write.IsSuccess) return write;
    
    			write = siemens.Write( "M202", 123f );
    			if (!write.IsSuccess) return write;
    
    			write = siemens.Write( "M206", "123456" );
    			if (!write.IsSuccess) return write;
    
    			return siemens.Write( "M100.0", true );
    		}
    

      嗯,这时候,就需要使用Then方法了,可以简化为:

    		public OperateResult StartPLC( ) => siemens.Write( "M200", (short)123 ).
    			Then( ( ) => siemens.Write( "M202", 123f ) ).
    			Then( ( ) => siemens.Write( "M206", "123456" ) ).
    			Then( ( ) => siemens.Write( "M100.0", true ) );
    

      

    一旦发生失败,就会立即回传。现在我们来看个更复杂的综合例子,这是一个现场流程中间的一个小环节,当AGV车到达库位后,需要通知PLC进行连串的交互,以及读取条码信息:

      

    		string barcode = string.Empty;
    		public OperateResult CheckSignalAfterAgvReach( )
    		{
    			// 通知PLC信息,AGV已经到达
    			OperateResult write = siemens.Write( "DB101.3.1", true );
    			if (!write.IsSuccess) return write;
    
    			// 等待PLC复位 允许AGV放胚信号 为false
    			OperateResult wait = siemens.Wait( "DB101.3.2", false );
    			if (wait.IsSuccess) return wait;
    
    			// 复位AGV放胚完成信号
    			write = siemens.Write( "DB101.3.1", false );
    			if (!write.IsSuccess) return write;
    
    			// 等待允许读取条码信息
    			wait = siemens.Wait( "DB101.1.3", true );
    			if (wait.IsSuccess) return wait;
    
    			// 读取条码的信息
    			var readBarCode = siemens.ReadString( "DB102.0" );
    			if (!readBarCode.IsSuccess) return readBarCode;
    
    			// 条码用于其他用途
    			barcode = readBarCode.Content;
    
    			// 将上料读取条码完成值true
    			write = siemens.Write( "DB101.1.4", true );
    			if (!write.IsSuccess) return write;
    
    			// 等待上料允许读取条码设置为false
    			wait = siemens.Wait( "DB101.1.3", false );
    			if (wait.IsSuccess) return wait;
    
    			// 复位上料条码读取完成信号
    			return siemens.Write( "DB101.1.4", false );
    		}
    

      那么这部分的代码可以简写为:

    		public OperateResult CheckSignalAfterAgvReach2( ) => siemens.Write( "DB101.3.1", true ).
    			Then( ( ) => siemens.Wait( "DB101.3.2", false ) ).
    			Then( ( ) => siemens.Write( "DB101.3.1", false ) ).
    			Then( ( ) => siemens.Wait( "DB101.1.3", true ) ).
    			Then( ( ) => siemens.ReadString( "DB102.0" ) ).
    			Then( m => { barcode = m; return siemens.Write( "DB101.1.4", true ); } ).
    			Then( ( ) => siemens.Wait( "DB101.1.3", false ) ).
    			Then( ( ) => siemens.Write( "DB101.1.4", false ) );
    
    		string barcode = string.Empty;
    

      emmmm,好像写多了,代码是简化了,可读性并没有提升很多,也是给了一个方向。

  • 相关阅读:
    git 去除对某个文件的版本控制
    10:08 小记
    写读书笔记
    恢复已删除且已添加至暂存区的文件
    第七周
    第六周
    软件测试
    短信获取
    Android-8
    增删改查
  • 原文地址:https://www.cnblogs.com/dathlin/p/13863115.html
Copyright © 2020-2023  润新知