对于PLC(可编程逻辑控制器)通信,我有一个抽象类PlcValueAbstract
并扩展泛型类PlcValue<T>
。我有这些类,所以我可以指定要读取的内容(T),但是将读取实现留给进行PLC通信/互操作的类。我需要能够同时读取其中的多个,这就是PlcValueAbstract
进来的地方。
现在我有一个方法ReadMultiple(IDictionary<string, PlcValueAbstract> values)
,它会将寄存器(PLC)中的值读入配对的PlcValueAbstract
。为此,我检查PlcValueAbstract
是PlcValue<int>
还是PlcValue<short>
(将扩展到更多类型)并处理byte[]
到PlcValue<>
转换({ {1}})。但是,现在我的代码变得混乱,每个(不同的)方法调用的类型检查(和异常)都使用IPAddress.NetworkToHostOrder(BitConverver.ToInt32(bytesFromLibrary))
上的T
类型。
不幸的是,我不能依赖从PlcValue<T>
到byte[]
的转换,因为我们使用不同品牌的PLC,具有不同的寄存器大小(或API类型)和不同的字节顺序,所以我不能限制我的任何T
代码仅支持PlcValue<T>
。
我有一种直觉,认为我的问题过于复杂,因此必须为每种支持的泛型类型执行拆分类型实现,这非常麻烦。 是否有解决方案我可以将所有类型的mumbo-jumbo移动到几种方法?
根据要求,这是实施的一部分:
PlcValueAbstract:
byte[]
PlcValue:
public abstract class PlcValueAbstract
{
internal PlcValueAbstract()
{
}
public abstract Type GetUnderlyingType();
}
SiemensPlc:
public class PlcValue<T> : PlcValueAbstract
{
public PlcValue(T value)
{
this.Value = value;
}
public T Value
{
get;
set;
}
public override Type GetUnderlyingType()
{
return typeof(T);
}
}
上面的代码有点简化,实际代码也处理不同数据类型的地址规范。
答案 0 :(得分:0)
我的想法是这样的事情应该有效:
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&lt; response.Length; i ++) { byte [] res = response [i]; PlcValueAbstract pv = values [i];
pv.SetValue(res);
}
此代码尚未经过测试或编译,但原则应该是合理的,即使需要进行一些调整才能使其正常工作。
你实际上是在改变你的类型检查代码,无需检查类型是什么,而是支持为每种数据类型设置类声明。
这个工作的主要原因是因为SetValue
的方法签名不需要知道底层类型是什么,它只需要采取一定的输入然后在内部做任何想要的事情,这样它就可以很容易在不知道任何其他事情的情况下被召唤。
答案 1 :(得分:0)
看起来你需要的可能是定义一个IPlcValue
接口,它包含查找项的基础类型的成员,以及将该类型转换为一系列字节或从一系列字节转换[例程转换可以编写一系列字节来使用流,或者可以接受沿着数组有效部分长度的一个字节数组和一个nextOffset
值,后者作为ref int
传递]。除此之外,还应定义IPlcValue<T>
接口,其中包含Value
类型T
成员。一旦完成了这些事情,就可以定义实现PlcIntegerMsbFirst
的类PlcIntegerLsbFirst
和/或IPlcValue<int>
,实现IPlcValue<String>
的PlcString [如果有任何PLC使用的话]具有IPlcValue项集合的代码可以将它们全部转换为字节序列或从字节序列转换而不必知道它们的基础类型;知道某些内容应该是数字的代码可以将其作为IPlcValue<int>
访问,而不必知道它是存储在MSB优先还是LSB优先等等。
答案 2 :(得分:0)
我终于花了一些时间思考这个问题并想出了一个解决方案。我添加了一个IntermediateReadResult
抽象类,该类传递给PlcValueContainerBase
(原始问题中的PlcValueAbstract
),然后将其传递给PlcValueContainer<T>
({{1}在原始问题中),它会告诉PlcValue<T>
它想要执行IntermediateReadResult
,其值为Action
。 T
反过来将此传递给实现类(下例中为IntermediateReadResult
)中的某些实现,这些实现将实际进行值转换。采用类似的方法执行写操作。读取和写入之间存在一些小的差异,因为对于多个数据对象的读取,我为每个数据容器构建一个MitsubishiIntermediateReadResult
,而对于写入,我只有一个IntermediateReadResult
。从下面的小例子中可以清楚地看到这一点,但可以想象,因为只有类型被传递(作为通用参数)到IntermediateWriteData
方法上。
解释结构的效果是,现在我可以使用以下一组PLC类型的实现类来实现使用Apply<TValue>
的数据读取和写入:
PlcValueContainer
BrandDataConverter
BrandIntermediateReadResult
BrandIntermediateWriteData
根据PLC供应商的API,可能还需要其他类,但当然也需要这些类。所有品牌类都存在于他们自己的BrandPlc
程序集中,因此如果我只需要一个(或两个),我就不会创建所有PLC品牌的依赖关系。我还有一些包装代码和一个抽象的PLC类来隐藏消费者类的内部API(我不希望消费者偶然应用一些BrandPlc
),但除此之外,以下代码示例涵盖了大部分内容是必需的:
CustomIntermediateReadResult