处理扩展非泛型基的泛型类型



对于PLC(可编程逻辑控制器(通信,我有一个抽象类PlcValueAbstract和扩展泛型类PlcValue<T>。我有这些类,所以我可以指定要读取的内容(T(,但将读取实现留给执行 PLC 通信/互操作的类。我需要能够同时阅读其中的多个,这就是PlcValueAbstract的用武之地。

现在我有一个方法ReadMultiple(IDictionary<string, PlcValueAbstract> values)它将值从寄存器(PLC(读取到配对PlcValueAbstract中。为此,我检查PlcValueAbstractPlcValue<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传递]。 除此之外,还应定义一个包含类型 TValue 成员的 IPlcValue<T> 接口。 一旦完成了这些事情,就可以定义实现IPlcValue<int>的类PlcIntegerMsbFirst和/或PlcIntegerLsbFirst,实现IPlcValue<String>的PlcString[如果任何PLC使用字符串]等。 具有 IPlcValue 项集合的代码可以将它们全部转换为字节序列或从字节序列转换,而无需了解它们的基础类型;知道某物应该是数字的代码可以IPlcValue<int>访问它,而不必知道它是存储 MSB 优先还是 LSB 优先等。

我终于花了一些时间思考这个问题,并提出了一个解决方案。我添加了一个传递给PlcValueContainerBaseIntermediateReadResult抽象类(原始问题中的PlcValueAbstract(,而又会将其传递给PlcValueContainer<T>(原始问题中的PlcValue<T>(,这将告诉IntermediateReadResult它想要执行值为 T 类型的ActionIntermediateReadResult反过来将其传递给实现类中的某个实现(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));
    }
}

最新更新