对于PLC(可编程逻辑控制器(通信,我有一个抽象类PlcValueAbstract
和扩展泛型类PlcValue<T>
。我有这些类,所以我可以指定要读取的内容(T(,但将读取实现留给执行 PLC 通信/互操作的类。我需要能够同时阅读其中的多个,这就是PlcValueAbstract
的用武之地。
现在我有一个方法ReadMultiple(IDictionary<string, PlcValueAbstract> values)
它将值从寄存器(PLC(读取到配对PlcValueAbstract
中。为此,我检查PlcValueAbstract
是PlcValue<int>
还是PlcValue<short>
(将扩展到更多类型(并处理byte[]
以PlcValue<>
转换(IPAddress.NetworkToHostOrder(BitConverver.ToInt32(bytesFromLibrary))
(。但是,现在我的代码变得混乱,每个(不同的(方法调用的类型检查(和异常(都使用PlcValue<T>
类型T
。
不幸的是,我也不能依赖从byte[]
到T
的转换,因为我们使用不同品牌的PLC,具有不同的寄存器大小(或API类型(和不同的字节序,所以我不能限制我的任何PlcValue<T>
代码仅支持byte[]
。
有一种直觉,我使问题过于复杂,因此必须为每个支持的泛型类型进行拆分类型实现,这真的很麻烦。有没有一种解决方案可以将所有类型的mumbo-jumbo移动到几种方法?
根据要求,这是实现的一部分:
PlcValueAbstract:
public abstract class PlcValueAbstract
{
internal PlcValueAbstract()
{
}
public abstract Type GetUnderlyingType();
}
价值:
public class PlcValue<T> : PlcValueAbstract
{
public PlcValue(T value)
{
this.Value = value;
}
public T Value
{
get;
set;
}
public override Type GetUnderlyingType()
{
return typeof(T);
}
}
西门子公司:
public class SiemensPlc
{
public void WriteMultiple(IDictionary<string, PlcValueAbstract> data)
{
IDictionary<string, byte[]> plcData = data.Select(e => BuildWrite(e.Key, e.Value))
.ToDictionary(x => x.Key, x => x.Value);
return library.Write(plcData);
}
public void Read(IDictionary<string, PlcValueAbstract> data)
{
byte[][] response = library.Read(data.Keys);
PlcValueAbstract[] values = data.Values.ToArray();
for (int i = 0; i < response.Length; i++)
{
byte[] res = response[i];
PlcValueAbstract pv = values[i];
if (pv is PlcValue<int>)
{
PlcValue<int> v = (PlcValue<int>)pv;
v.Value = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(res, 0));
}
else if (pv is PlcValue<short>)
{
PlcValue<short> v = (PlcValue<short>)pv;
v.Value = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(res, 0));
}
else
{
throw new Exception("Invalid type");
}
}
}
private KeyValuePair<string, byte[]> BuildWrite(string address, PlcValueAbstract value)
{
byte[] data;
if (value is PlcValue<int>)
{
data = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(((PlcValue<int>)value).Value));
}
else if (value is PlcValue<short>)
{
data = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(((PlcValue<short>)value).Value));
}
else
{
throw new ArgumentException("Value type is not supported", "value");
}
return new KeyValuePair<string, byte[]>(address, data);
}
上面的代码有点简化,实际代码还处理不同数据类型的地址规范。
我的想法是这样的事情应该有效:
public abstract class PlcValueAbstract
{
internal PlcValueAbstract()
{
}
public abstract Type GetUnderlyingType();
public abstract SetValue(byte[] bytes);
}
public class PlcValueInt : PlcValue<int>
{
public override SetValue(byte[] bytes)
{
Value = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(bytes, 0));
}
}
我已经将泛型类抽象化(未显示(,然后在基本抽象类上有一个 setValue 方法,该方法采用一个字节数组,它的工作是设置值。
然后,从中派生具体类,每种类型对应一个类,只需要实现一种方法即可将结果解析为值。
然后你的问题代码将看起来像:
for (int i = 0; i <响应。长度;i++(>
pv.SetValue(res);
}
这段代码还没有经过测试或编译,但原则应该是合理的,即使它需要一些调整才能让它工作。
您实际上是在打乱类型检查代码,无需检查类型是什么,而是为每个数据类型使用类声明。
这工作的主要原因是因为 SetValue
的方法签名不需要知道底层类型是什么,它只需要接受某个输入,然后在内部执行任何它想要的操作,因此可以轻松调用它而无需知道更多内容。
看起来您需要的可能是定义一个IPlcValue
接口,其中包含用于查找项目的基础类型的成员,以及将该类型转换为一系列字节或从一系列字节转换[从一系列字节转换的例程可以写入为使用流,或者可以沿数组有效部分的长度接受字节数组和nextOffset
值,后者作为ref int
传递]。 除此之外,还应定义一个包含类型 T
的 Value
成员的 IPlcValue<T>
接口。 一旦完成了这些事情,就可以定义实现IPlcValue<int>
的类PlcIntegerMsbFirst
和/或PlcIntegerLsbFirst
,实现IPlcValue<String>
的PlcString[如果任何PLC使用字符串]等。 具有 IPlcValue 项集合的代码可以将它们全部转换为字节序列或从字节序列转换,而无需了解它们的基础类型;知道某物应该是数字的代码可以IPlcValue<int>
访问它,而不必知道它是存储 MSB 优先还是 LSB 优先等。
我终于花了一些时间思考这个问题,并提出了一个解决方案。我添加了一个传递给PlcValueContainerBase
的IntermediateReadResult
抽象类(原始问题中的PlcValueAbstract
(,而又会将其传递给PlcValueContainer<T>
(原始问题中的PlcValue<T>
(,这将告诉IntermediateReadResult
它想要执行值为 T
类型的Action
。IntermediateReadResult
反过来将其传递给实现类中的某个实现(MitsubishiIntermediateReadResult
下面的示例中(,这些实现将实际执行值转换。执行写入也采用了类似的方法。读取和写入之间存在一些小差异,因为对于多个数据对象的读取,我为每个数据容器构建一个IntermediateReadResult
,而对于写入,我只有一个IntermediateWriteData
。从下面的小示例中看不清这一点,但可以计算出来,因为只有类型(作为泛型参数(传递给 Apply<TValue>
方法。
所解释的结构的效果是,现在我可以为PLC类型提供以下一组实现类,以使用PlcValueContainer
实现数据读取和写入:
-
BrandDataConverter
-
BrandIntermediateReadResult
-
BrandIntermediateWriteData
-
BrandPlc
根据 PLC 供应商的 API,可能需要其他类,但当然,这些类始终是必需的。所有 Brand 类都位于它们自己的 BrandPlc
程序集中,因此如果我只需要一个(或两个(,我不会创建对所有 PLC 品牌的依赖关系。我还有一些包装代码和一个抽象的 PLC 类来隐藏消费者类的内部 API(我不希望消费者意外应用一些CustomIntermediateReadResult
(,但除此之外,下面的代码示例涵盖了大部分需要的内容:
public abstract class PlcValueContainerBase
{
...
internal abstract void ApplyReadResult(IntermediateReadResult intermediateReadResult);
internal abstract void ContributeWrite(IntermediateWriteData intermediateWriteData);
...
}
public class PlcValueContainer<TValue> : PlcValueContainerBase
{
public TValue Value { get; set; }
internal override void ApplyReadResult(IntermediateReadResult intermediateReadResult)
{
intermediateReadResult.Apply<TValue>(value => Value = value);
}
internal override void ContributeWrite(IntermediateWriteData intermediateWriteData)
{
intermediateWriteData.Append(Address, Value, Length);
}
}
public abstract class IntermediateReadResult
{
protected internal abstract void Apply<TValue>(Action<TValue> valueAction);
}
class MitsubishiIntermediateReadResult : IntermediateReadResult
{
private readonly short[] data;
public MitsubishiIntermediateReadResult(short[] data)
{
this.data = data;
}
protected override void Apply<TValue>(Action<TValue> valueAction)
{
valueAction(MitsubishiDataConverter.ConvertFromPlc<TValue>(data));
}
}