| ¶Ô±ÈÐÂÎļþ |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Linq; |
| | | using System.Text; |
| | | using WIDESEAWCS_S7Simulator.Core.Entities; |
| | | using WIDESEAWCS_S7Simulator.Core.Interfaces; |
| | | |
| | | namespace WIDESEAWCS_S7Simulator.Core.Memory |
| | | { |
| | | /// <summary> |
| | | /// å
ååå¨å®ç° |
| | | /// ç®¡çææS7 PLCå
ååºåï¼æä¾ç»ä¸çå°åè®¿é®æ¥å£ |
| | | /// </summary> |
| | | public class MemoryStore : IMemoryStore |
| | | { |
| | | /// <summary> |
| | | /// æ è¯å¯¹è±¡æ¯å¦å·²è¢«éæ¾ |
| | | /// </summary> |
| | | private bool _disposed = false; |
| | | |
| | | /// <summary> |
| | | /// Måºï¼ä½åå¨å¨ï¼ |
| | | /// </summary> |
| | | private readonly MRegion _mRegion; |
| | | |
| | | /// <summary> |
| | | /// DBåºï¼æ°æ®åï¼ |
| | | /// </summary> |
| | | private readonly DBRegion _dbRegion; |
| | | |
| | | /// <summary> |
| | | /// Iåºï¼è¾å
¥åºï¼ |
| | | /// </summary> |
| | | private readonly IRegion _iRegion; |
| | | |
| | | /// <summary> |
| | | /// Qåºï¼è¾åºåºï¼ |
| | | /// </summary> |
| | | private readonly QRegion _qRegion; |
| | | |
| | | /// <summary> |
| | | /// Tåºï¼å®æ¶å¨åºï¼ |
| | | /// </summary> |
| | | private readonly TRegion _tRegion; |
| | | |
| | | /// <summary> |
| | | /// Cåºï¼è®¡æ°å¨åºï¼ |
| | | /// </summary> |
| | | private readonly CRegion _cRegion; |
| | | |
| | | /// <summary> |
| | | /// å
ååºåé
ç½® |
| | | /// </summary> |
| | | private readonly MemoryRegionConfig _config; |
| | | |
| | | /// <summary> |
| | | /// æé 彿° |
| | | /// </summary> |
| | | /// <param name="config">å
ååºåé
ç½®</param> |
| | | public MemoryStore(MemoryRegionConfig config) |
| | | { |
| | | _config = config ?? throw new ArgumentNullException(nameof(config)); |
| | | |
| | | _mRegion = new MRegion(config.MRegionSize); |
| | | _dbRegion = new DBRegion(1, config.DBBlockSize); |
| | | _iRegion = new IRegion(config.IRegionSize); |
| | | _qRegion = new QRegion(config.QRegionSize); |
| | | _tRegion = new TRegion(config.TRegionCount); |
| | | _cRegion = new CRegion(config.CRegionCount); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 读ååèæ°æ® |
| | | /// </summary> |
| | | public byte[] ReadBytes(string address, ushort length) |
| | | { |
| | | if (_disposed) |
| | | throw new ObjectDisposedException(nameof(MemoryStore)); |
| | | |
| | | if (string.IsNullOrEmpty(address)) |
| | | throw new ArgumentException("å°åä¸è½ä¸ºç©º", nameof(address)); |
| | | |
| | | var (regionType, offset, dbNumber, bitOffset) = ParseAddress(address); |
| | | |
| | | return regionType switch |
| | | { |
| | | "M" => _mRegion.Read(offset, length), |
| | | "DB" => _dbRegion.Read(dbNumber.Value, offset, length), |
| | | "I" => _iRegion.Read(offset, length), |
| | | "Q" => _qRegion.Read(offset, length), |
| | | "T" => throw new NotSupportedException("Tåºä¸æ¯æç´æ¥è¯»ååèï¼è¯·ä½¿ç¨Read<T>"), |
| | | "C" => throw new NotSupportedException("Cåºä¸æ¯æç´æ¥è¯»ååèï¼è¯·ä½¿ç¨Read<T>"), |
| | | _ => throw new ArgumentException($"䏿¯æçå°ååºå: {regionType}") |
| | | }; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 读åæå®ç±»åæ°æ® |
| | | /// </summary> |
| | | public T Read<T>(string address) where T : struct |
| | | { |
| | | if (_disposed) |
| | | throw new ObjectDisposedException(nameof(MemoryStore)); |
| | | |
| | | if (string.IsNullOrEmpty(address)) |
| | | throw new ArgumentException("å°åä¸è½ä¸ºç©º", nameof(address)); |
| | | |
| | | var (regionType, offset, dbNumber, bitOffset) = ParseAddress(address); |
| | | |
| | | return regionType switch |
| | | { |
| | | "M" => ReadMRegion<T>(offset), |
| | | "DB" => ReadDBRegion<T>(dbNumber.Value, offset), |
| | | "I" => ReadIRegion<T>(offset, bitOffset), |
| | | "Q" => ReadQRegion<T>(offset, bitOffset), |
| | | "T" => ReadTRegion<T>(offset), |
| | | "C" => ReadCRegion<T>(offset), |
| | | _ => throw new ArgumentException($"䏿¯æçå°ååºå: {regionType}") |
| | | }; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// åå
¥åèæ°æ® |
| | | /// </summary> |
| | | public void WriteBytes(string address, byte[] data) |
| | | { |
| | | if (_disposed) |
| | | throw new ObjectDisposedException(nameof(MemoryStore)); |
| | | |
| | | if (string.IsNullOrEmpty(address)) |
| | | throw new ArgumentException("å°åä¸è½ä¸ºç©º", nameof(address)); |
| | | |
| | | if (data == null) |
| | | throw new ArgumentNullException(nameof(data)); |
| | | |
| | | var (regionType, offset, dbNumber, bitOffset) = ParseAddress(address); |
| | | |
| | | switch (regionType) |
| | | { |
| | | case "M": |
| | | _mRegion.Write(offset, data); |
| | | break; |
| | | case "DB": |
| | | _dbRegion.Write(dbNumber.Value, offset, data); |
| | | break; |
| | | case "I": |
| | | _iRegion.Write(offset, data); |
| | | break; |
| | | case "Q": |
| | | _qRegion.Write(offset, data); |
| | | break; |
| | | case "T": |
| | | throw new NotSupportedException("Tåºä¸æ¯æç´æ¥åå
¥åè"); |
| | | case "C": |
| | | throw new NotSupportedException("Cåºä¸æ¯æç´æ¥åå
¥åè"); |
| | | default: |
| | | throw new ArgumentException($"䏿¯æçå°ååºå: {regionType}"); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// åå
¥æå®ç±»åæ°æ® |
| | | /// </summary> |
| | | public void Write<T>(string address, T value) where T : struct |
| | | { |
| | | if (_disposed) |
| | | throw new ObjectDisposedException(nameof(MemoryStore)); |
| | | |
| | | if (string.IsNullOrEmpty(address)) |
| | | throw new ArgumentException("å°åä¸è½ä¸ºç©º", nameof(address)); |
| | | |
| | | var (regionType, offset, dbNumber, bitOffset) = ParseAddress(address); |
| | | |
| | | switch (regionType) |
| | | { |
| | | case "M": |
| | | WriteMRegion(offset, value); |
| | | break; |
| | | case "DB": |
| | | WriteDBRegion(dbNumber.Value, offset, value); |
| | | break; |
| | | case "I": |
| | | WriteIRegion(offset, bitOffset, value); |
| | | break; |
| | | case "Q": |
| | | WriteQRegion(offset, bitOffset, value); |
| | | break; |
| | | case "T": |
| | | WriteTRegion(offset, value); |
| | | break; |
| | | case "C": |
| | | WriteCRegion(offset, value); |
| | | break; |
| | | default: |
| | | throw new ArgumentException($"䏿¯æçå°ååºå: {regionType}"); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// è·åå
ååºå |
| | | /// </summary> |
| | | public IMemoryRegion GetRegion(string regionType) |
| | | { |
| | | if (_disposed) |
| | | throw new ObjectDisposedException(nameof(MemoryStore)); |
| | | |
| | | return regionType.ToUpper() switch |
| | | { |
| | | "M" => _mRegion, |
| | | "DB" => _dbRegion, |
| | | "I" => _iRegion, |
| | | "Q" => _qRegion, |
| | | "T" => _tRegion, |
| | | "C" => _cRegion, |
| | | _ => throw new ArgumentException($"䏿¯æçå
ååºå: {regionType}") |
| | | }; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// æ¸
空ææå
å |
| | | /// </summary> |
| | | public void Clear() |
| | | { |
| | | if (_disposed) |
| | | throw new ObjectDisposedException(nameof(MemoryStore)); |
| | | |
| | | _mRegion.Clear(); |
| | | _dbRegion.Clear(); |
| | | _iRegion.Clear(); |
| | | _qRegion.Clear(); |
| | | _tRegion.Clear(); |
| | | _cRegion.Clear(); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 导åºå
åæ°æ® |
| | | /// </summary> |
| | | public Dictionary<string, byte[]> Export() |
| | | { |
| | | if (_disposed) |
| | | throw new ObjectDisposedException(nameof(MemoryStore)); |
| | | |
| | | return new Dictionary<string, byte[]> |
| | | { |
| | | ["M"] = _mRegion.Read(0, (ushort)_mRegion.Size), |
| | | ["DB"] = ExportDBRegion(), |
| | | ["I"] = _iRegion.Read(0, (ushort)_iRegion.Size), |
| | | ["Q"] = _qRegion.Read(0, (ushort)_qRegion.Size), |
| | | ["T"] = _tRegion.Read(0, (ushort)_tRegion.Size), |
| | | ["C"] = _cRegion.Read(0, (ushort)_cRegion.Size) |
| | | }; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 导å
¥å
åæ°æ® |
| | | /// </summary> |
| | | public void Import(Dictionary<string, byte[]> data) |
| | | { |
| | | if (_disposed) |
| | | throw new ObjectDisposedException(nameof(MemoryStore)); |
| | | |
| | | if (data == null) |
| | | throw new ArgumentNullException(nameof(data)); |
| | | |
| | | if (data.ContainsKey("M")) |
| | | _mRegion.Write(0, data["M"]); |
| | | if (data.ContainsKey("DB")) |
| | | ImportDBRegion(data["DB"]); |
| | | if (data.ContainsKey("I")) |
| | | _iRegion.Write(0, data["I"]); |
| | | if (data.ContainsKey("Q")) |
| | | _qRegion.Write(0, data["Q"]); |
| | | if (data.ContainsKey("T")) |
| | | _tRegion.Write(0, data["T"]); |
| | | if (data.ContainsKey("C")) |
| | | _cRegion.Write(0, data["C"]); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// è§£æS7å°åæ ¼å¼ |
| | | /// æ¯ææ ¼å¼: M100, DB1.DBD0, I0.0, Q0.0, T1, C1 |
| | | /// </summary> |
| | | private (string regionType, ushort offset, ushort? dbNumber, byte? bitOffset) ParseAddress(string address) |
| | | { |
| | | address = address.Trim().ToUpper(); |
| | | |
| | | // DBåå°å: DB1.DBD0, DB1.DBW10, DB1.DBB20 |
| | | if (address.StartsWith("DB")) |
| | | { |
| | | var parts = address.Split(new[] { '.' }, 2); |
| | | var dbPart = parts[0]; // DB1 |
| | | var offsetPart = parts.Length > 1 ? parts[1] : null; // DBD0, DBW10, etc. |
| | | |
| | | var dbNumber = ushort.Parse(dbPart.Substring(2)); |
| | | ushort offset = 0; |
| | | |
| | | if (offsetPart != null) |
| | | { |
| | | // è§£æå移类å: DBD(4åè), DBW(2åè), DBB(1åè) |
| | | if (offsetPart.StartsWith("DBD")) |
| | | offset = ushort.Parse(offsetPart.Substring(3)); |
| | | else if (offsetPart.StartsWith("DBW")) |
| | | offset = ushort.Parse(offsetPart.Substring(3)); |
| | | else if (offsetPart.StartsWith("DBB")) |
| | | offset = ushort.Parse(offsetPart.Substring(3)); |
| | | else |
| | | offset = ushort.Parse(offsetPart); |
| | | } |
| | | |
| | | return ("DB", offset, dbNumber, null); |
| | | } |
| | | |
| | | // 带ä½å°å: I0.0, Q0.0, M0.0 |
| | | if (address.Contains(".")) |
| | | { |
| | | var parts = address.Split('.'); |
| | | var regionType = new string(parts[0].TakeWhile(char.IsLetter).ToArray()); |
| | | var byteOffset = ushort.Parse(new string(parts[0].SkipWhile(char.IsLetter).ToArray())); |
| | | var bitOffset = byte.Parse(parts[1]); |
| | | |
| | | return (regionType, byteOffset, null, (byte?)bitOffset); |
| | | } |
| | | |
| | | // 宿¶å¨/计æ°å¨: T1, C1 |
| | | if (address.StartsWith("T") || address.StartsWith("C")) |
| | | { |
| | | var regionType = address[0].ToString(); |
| | | var number = ushort.Parse(address.Substring(1)); |
| | | return (regionType, number, null, null); |
| | | } |
| | | |
| | | // æ®éå°å: M100, I100, Q100 |
| | | var region = new string(address.TakeWhile(char.IsLetter).ToArray()); |
| | | var addr = ushort.Parse(new string(address.SkipWhile(char.IsLetter).ToArray())); |
| | | return (region, addr, null, null); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 读åMåºæ°æ® |
| | | /// </summary> |
| | | private T ReadMRegion<T>(ushort offset) where T : struct |
| | | { |
| | | var size = System.Runtime.InteropServices.Marshal.SizeOf<T>(); |
| | | var data = _mRegion.Read(offset, (ushort)size); |
| | | |
| | | if (typeof(T) == typeof(bool)) |
| | | return default; // Måºä¸æ¯æbool读åï¼åºè¯¥ç¨Mx.xæ ¼å¼ |
| | | |
| | | return ConvertFromBytes<T>(data); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 读åDBåºæ°æ® |
| | | /// </summary> |
| | | private T ReadDBRegion<T>(ushort dbNumber, ushort offset) where T : struct |
| | | { |
| | | return typeof(T).Name switch |
| | | { |
| | | "Boolean" => (T)(object)_dbRegion.ReadBool(dbNumber, offset, 0), |
| | | "Int16" => (T)(object)_dbRegion.ReadInt(dbNumber, offset), |
| | | "Int32" => (T)(object)_dbRegion.ReadDInt(dbNumber, offset), |
| | | "Single" => (T)(object)_dbRegion.ReadReal(dbNumber, offset), |
| | | "String" => (T)(object)_dbRegion.ReadString(dbNumber, offset, 254), |
| | | _ => ConvertFromBytes<T>(_dbRegion.Read(dbNumber, offset, (ushort)System.Runtime.InteropServices.Marshal.SizeOf<T>())) |
| | | }; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 读åIåºæ°æ® |
| | | /// </summary> |
| | | private T ReadIRegion<T>(ushort offset, byte? bitOffset) where T : struct |
| | | { |
| | | if (typeof(T) == typeof(bool) && bitOffset.HasValue) |
| | | return (T)(object)_iRegion.ReadBit(offset, bitOffset.Value); |
| | | |
| | | var size = System.Runtime.InteropServices.Marshal.SizeOf<T>(); |
| | | var data = _iRegion.Read(offset, (ushort)size); |
| | | return ConvertFromBytes<T>(data); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 读åQåºæ°æ® |
| | | /// </summary> |
| | | private T ReadQRegion<T>(ushort offset, byte? bitOffset) where T : struct |
| | | { |
| | | if (typeof(T) == typeof(bool) && bitOffset.HasValue) |
| | | return (T)(object)_qRegion.ReadBit(offset, bitOffset.Value); |
| | | |
| | | var size = System.Runtime.InteropServices.Marshal.SizeOf<T>(); |
| | | var data = _qRegion.Read(offset, (ushort)size); |
| | | return ConvertFromBytes<T>(data); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 读åTåºæ°æ® |
| | | /// </summary> |
| | | private T ReadTRegion<T>(ushort timerNumber) where T : struct |
| | | { |
| | | return (T)(object)_tRegion.ReadTimer(timerNumber); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 读åCåºæ°æ® |
| | | /// </summary> |
| | | private T ReadCRegion<T>(ushort counterNumber) where T : struct |
| | | { |
| | | return (T)(object)_cRegion.ReadCounter(counterNumber); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// åå
¥Måºæ°æ® |
| | | /// </summary> |
| | | private void WriteMRegion<T>(ushort offset, T value) where T : struct |
| | | { |
| | | var data = ConvertToBytes(value); |
| | | _mRegion.Write(offset, data); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// åå
¥DBåºæ°æ® |
| | | /// </summary> |
| | | private void WriteDBRegion<T>(ushort dbNumber, ushort offset, T value) where T : struct |
| | | { |
| | | switch (typeof(T).Name) |
| | | { |
| | | case "Boolean": |
| | | _dbRegion.WriteBool(dbNumber, offset, 0, (bool)(object)value); |
| | | break; |
| | | case "Int16": |
| | | _dbRegion.WriteInt(dbNumber, offset, (short)(object)value); |
| | | break; |
| | | case "Int32": |
| | | _dbRegion.WriteDInt(dbNumber, offset, (int)(object)value); |
| | | break; |
| | | case "Single": |
| | | _dbRegion.WriteReal(dbNumber, offset, (float)(object)value); |
| | | break; |
| | | case "String": |
| | | _dbRegion.WriteString(dbNumber, offset, (string)(object)value, 254); |
| | | break; |
| | | default: |
| | | var data = ConvertToBytes(value); |
| | | _dbRegion.Write(dbNumber, offset, data); |
| | | break; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// åå
¥Iåºæ°æ® |
| | | /// </summary> |
| | | private void WriteIRegion<T>(ushort offset, byte? bitOffset, T value) where T : struct |
| | | { |
| | | if (typeof(T) == typeof(bool) && bitOffset.HasValue) |
| | | { |
| | | _iRegion.WriteBit(offset, bitOffset.Value, (bool)(object)value); |
| | | return; |
| | | } |
| | | |
| | | var data = ConvertToBytes(value); |
| | | _iRegion.Write(offset, data); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// åå
¥Qåºåºæ°æ® |
| | | /// </summary> |
| | | private void WriteQRegion<T>(ushort offset, byte? bitOffset, T value) where T : struct |
| | | { |
| | | if (typeof(T) == typeof(bool) && bitOffset.HasValue) |
| | | { |
| | | _qRegion.WriteBit(offset, bitOffset.Value, (bool)(object)value); |
| | | return; |
| | | } |
| | | |
| | | var data = ConvertToBytes(value); |
| | | _qRegion.Write(offset, data); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// åå
¥Tåºæ°æ® |
| | | /// </summary> |
| | | private void WriteTRegion<T>(ushort timerNumber, T value) where T : struct |
| | | { |
| | | _tRegion.WriteTimer(timerNumber, (ushort)(object)value); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// åå
¥Cåºæ°æ® |
| | | /// </summary> |
| | | private void WriteCRegion<T>(ushort counterNumber, T value) where T : struct |
| | | { |
| | | _cRegion.WriteCounter(counterNumber, (ushort)(object)value); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// ä»åèæ°ç»è½¬æ¢ä¸ºå¼ç±»å |
| | | /// </summary> |
| | | private T ConvertFromBytes<T>(byte[] data) where T : struct |
| | | { |
| | | if (typeof(T) == typeof(short)) |
| | | return (T)(object)(short)((data[0] << 8) | data[1]); |
| | | |
| | | if (typeof(T) == typeof(int)) |
| | | return (T)(object)((data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]); |
| | | |
| | | if (typeof(T) == typeof(ushort)) |
| | | return (T)(object)((ushort)((data[0] << 8) | data[1])); |
| | | |
| | | if (typeof(T) == typeof(float)) |
| | | return (T)(object)BitConverter.ToSingle(data, 0); |
| | | |
| | | return default; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// å°å¼ç±»å转æ¢ä¸ºåèæ°ç» |
| | | /// </summary> |
| | | private byte[] ConvertToBytes<T>(T value) where T : struct |
| | | { |
| | | if (typeof(T) == typeof(short)) |
| | | return new[] { (byte)((short)(object)value >> 8), (byte)((short)(object)value & 0xFF) }; |
| | | |
| | | if (typeof(T) == typeof(int)) |
| | | return new[] { |
| | | (byte)((int)(object)value >> 24), |
| | | (byte)(((int)(object)value >> 16) & 0xFF), |
| | | (byte)(((int)(object)value >> 8) & 0xFF), |
| | | (byte)((int)(object)value & 0xFF) |
| | | }; |
| | | |
| | | if (typeof(T) == typeof(ushort)) |
| | | return new[] { (byte)((ushort)(object)value >> 8), (byte)((ushort)(object)value & 0xFF) }; |
| | | |
| | | if (typeof(T) == typeof(float)) |
| | | return BitConverter.GetBytes((float)(object)value); |
| | | |
| | | if (typeof(T) == typeof(bool)) |
| | | return new[] { (byte)((bool)(object)value ? 1 : 0) }; |
| | | |
| | | throw new NotSupportedException($"䏿¯æçç±»å: {typeof(T).Name}"); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 导åºDBåºæ°æ® |
| | | /// </summary> |
| | | private byte[] ExportDBRegion() |
| | | { |
| | | // ç®åçæ¬ï¼å¯¼åºææDBåçè¿ç»æ°æ® |
| | | // å®é
å®ç°å¯è½éè¦æ´å¤æçåºååæ ¼å¼ |
| | | var result = new List<byte>(); |
| | | for (ushort i = 1; i <= _config.DBBlockCount; i++) |
| | | { |
| | | var blockData = _dbRegion.Read(i, 0, (ushort)_config.DBBlockSize); |
| | | result.AddRange(blockData); |
| | | } |
| | | return result.ToArray(); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 导å
¥DBåºæ°æ® |
| | | /// </summary> |
| | | private void ImportDBRegion(byte[] data) |
| | | { |
| | | // ç®åçæ¬ï¼æé¡ºåºå¯¼å
¥ææDBå |
| | | int offset = 0; |
| | | for (ushort i = 1; i <= _config.DBBlockCount && offset < data.Length; i++) |
| | | { |
| | | var blockSize = Math.Min(_config.DBBlockSize, data.Length - offset); |
| | | var blockData = new byte[blockSize]; |
| | | Array.Copy(data, offset, blockData, 0, blockSize); |
| | | _dbRegion.Write(i, 0, blockData); |
| | | offset += blockSize; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// éæ¾èµæº |
| | | /// </summary> |
| | | public void Dispose() |
| | | { |
| | | Dispose(true); |
| | | GC.SuppressFinalize(this); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// éæ¾èµæº |
| | | /// </summary> |
| | | /// <param name="disposing">æ¯å¦æ£å¨éæ¾æç®¡èµæº</param> |
| | | protected virtual void Dispose(bool disposing) |
| | | { |
| | | if (!_disposed) |
| | | { |
| | | if (disposing) |
| | | { |
| | | _mRegion?.Dispose(); |
| | | _dbRegion?.Dispose(); |
| | | _iRegion?.Dispose(); |
| | | _qRegion?.Dispose(); |
| | | _tRegion?.Dispose(); |
| | | _cRegion?.Dispose(); |
| | | } |
| | | _disposed = true; |
| | | } |
| | | } |
| | | } |
| | | } |