简体   繁体   English

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

[英]Handling generic types extending non-generic base

For PLC (Programmable Logic Controller) communication I have an abstract class PlcValueAbstract and extending generic class PlcValue<T> . 对于PLC(可编程逻辑控制器)通信,我有一个抽象类PlcValueAbstract和一个扩展的通用类PlcValue<T> I have these classes so I can specify what to read (T) but to leave the read implementation up to the class doing PLC communication / interop. 我有这些类,因此我可以指定要读取的内容(T)​​,但将读取的实现留给进行PLC通信/互操作的类使用。 I need the ability to read multiple of these at the same time, that's where the PlcValueAbstract comes in. 我需要同时读取多个这些的能力,这就是PlcValueAbstract出现的地方。

Right now I have a method ReadMultiple(IDictionary<string, PlcValueAbstract> values) which will read values from registers (PLC) into the paired PlcValueAbstract . 现在,我有一个ReadMultiple(IDictionary<string, PlcValueAbstract> values)方法,该方法将从寄存器(PLC)中读取值到成对的PlcValueAbstract To achieve this, I check if the PlcValueAbstract is either PlcValue<int> or PlcValue<short> (will be extended to more types) and handle byte[] to PlcValue<> conversion ( IPAddress.NetworkToHostOrder(BitConverver.ToInt32(bytesFromLibrary)) ). 为此,我检查PlcValueAbstractPlcValue<int>还是PlcValue<short> (将扩展为更多类型),并处理byte[]PlcValue<>转换( IPAddress.NetworkToHostOrder(BitConverver.ToInt32(bytesFromLibrary)) )。 However, now my code becomes cluttered with type checks (and exceptions) for every (different) method invocation which uses type T on PlcValue<T> . 但是,现在我的代码变得很混乱,因为每个(不同的)方法调用都使用类型检查(和异常),该方法调用在PlcValue<T>上使用类型T

Unfortunately I can't rely on conversion from byte[] to T either, because we use different brands of PLC's with different register sizes (or API types) and different endianness, so I can't constrain any of my PlcValue<T> code to support byte[] only. 不幸的是,我也不能依靠从byte[]T转换,因为我们使用了不同品牌的PLC,它们具有不同的寄存器大小(或API类型)和不同的字节序,所以我不能约束我的任何PlcValue<T>代码仅支持byte[]

I have a gut feeling that I'm overcomplicating the issue and thus having to do split type implementations for every supported generic type, which is really cumbersome. 我有种直觉,觉得问题太复杂了,因此必须为每种受支持的泛型类型执行拆分类型实现,这确实很麻烦。 Is there a solution where I can move all the type mumbo-jumbo to just a few methods? 有没有一种解决方案,可以将所有类型的mumbo-jumbo移至仅几种方法?

On request here's part of the implementation: 根据要求,这是实现的一部分:

PlcValueAbstract: PlcValueAbstract:

public abstract class PlcValueAbstract
{
    internal PlcValueAbstract()
    {
    }

    public abstract Type GetUnderlyingType();
}

PlcValue: PlcValue:

public class PlcValue<T> : PlcValueAbstract
{
    public PlcValue(T value)
    {
        this.Value = value;
    }

    public T Value
    {
        get;
        set;
    }

    public override Type GetUnderlyingType()
    {
        return typeof(T);
    }
}

SiemensPlc: 西门子有限公司:

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);
    }

The above code is a bit of a simplification, the actual code also handles address specification for the different data types. 上面的代码有点简化,实际的代码也处理不同数据类型的地址规范。

My thinking is that something like this should work: 我的想法是这样的事情应该起作用:

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));
    }
}

I've made the generic class abstract (not shown) and then there is a setValue method on the base abstract class that takes a byte array and its job is to set the value. 我已经使通用类抽象(未显示),然后在基本抽象类上有一个setValue方法,该方法采用字节数组,其工作是设置值。

You then derive your concrete classes from this, one for each type you need, and that just needs to implement the one method to parse your result into the value. 然后,您可以从中派生出具体的类,每种类型都需要一个具体的类,而只需实现一个方法即可将结果解析为值。

Then your problem code will just look like: 然后,您的问题代码将如下所示:

for (int i = 0; i < response.Length; i++) { byte[] res = response[i]; for(int i = 0; i <response.Length; i ++){byte [] res = response [i]; PlcValueAbstract pv = values[i]; PlcValueAbstract pv = values [i];

    pv.SetValue(res);

} }

This code has not been tested or compiled but the principles should be sound, even if it needs a bit of tweaking to get it working. 该代码尚未经过测试或编译,但是原理应该是正确的,即使需要一些调整才能使其正常工作。

You are essentially shuffling your type check code around, removing the need for checking what the type is in favour of having a class declaration for each of your datatypes. 本质上,您是在改组类型检查代码,从而无需检查类型,而需要为每个数据类型都具有类声明。

The main reason this works is because the method signature for SetValue doesn't need to know what the underlying type is, it just needs to take a certain input and then does whatever it wants internally so it can easily be called without knowing anything more. 起作用的主要原因是因为SetValue的方法签名不需要知道底层类型是什么,它只需要接受一定的输入然后在内部做任何想要的事情即可轻松地调用它,而无需了解更多信息。

It looks as though what you need may be to define an IPlcValue interface which contains members to find an item's underlying type, as well as to convert that type to or from a series of bytes [the routine to convert from a series of bytes could be written to use a stream, or could accept an array of bytes along the length of the valid portion of the array and a nextOffset value, the latter being passed as a ref int ]. 似乎您需要的是定义一个IPlcValue接口,该接口包含用于查找项目的基础类型的成员,以及将该类型与一系列字节进行相互转换(从一系列字节进行转换的例程可以是为使用流而编写,或者可以接受沿数组有效部分长度的字节数组和nextOffset值,后者作为ref int ]传递。 In addition to that, one should define an IPlcValue<T> interface which contains a Value member of type T . 除此之外,还应该定义一个IPlcValue<T>接口,其中包含类型TValue成员。 Once one has done those things, one could define class(es) PlcIntegerMsbFirst and/or PlcIntegerLsbFirst which would implement IPlcValue<int> , a PlcString which implements IPlcValue<String> [if any PLCs use strings], etc. Code which has a collection of IPlcValue items could convert them all to or from a sequence of bytes without having to know about their underlying types; 一旦一个已完成的那些事情,一个可以定义类(ES) PlcIntegerMsbFirst和/或PlcIntegerLsbFirst这将实现IPlcValue<int> ,它实现了一个PlcString IPlcValue<String> [如果任何PLC使用字符串]等编码具有集IPlcValue项可以将它们全部转换为字节序列或从字节序列转换为它们,而无需了解其底层类型; code which knows that something should be a number could access it as IPlcValue<int> without having to know whether it was stored MSB-first or LSB-first, etc. 知道某些东西应该是数字的代码可以作为IPlcValue<int>来访问它,而不必知道它是以MSB优先还是LSB优先存储的,等等。

I finally spent some time and thought on this issue and came up with a solution. 我终于花了一些时间思考这个问题,并提出了解决方案。 I added an IntermediateReadResult abstract class that is passed on to the PlcValueContainerBase ( PlcValueAbstract in the original question), which in turn will pass it to PlcValueContainer<T> ( PlcValue<T> in the original question), which will tell the IntermediateReadResult that it wants to perform an Action with a value of type T . 我添加了一个IntermediateReadResult抽象类,该抽象类传递给PlcValueContainerBase (原始问题中为PlcValueAbstract ),然后将其传递给PlcValueContainer<T> (原始问题中为PlcValue<T> ), PlcValue<T>将告诉IntermediateReadResult它想要执行一个T类型的值的Action The IntermediateReadResult in turn passes this on to some implementation in the implementation class ( MitsubishiIntermediateReadResult in the example below) that will actually do the value conversion. IntermediateReadResult依次将其传递给实现类中的某个实现(在下面的示例中为MitsubishiIntermediateReadResult ),该类实际上将进行值转换。 A similar approach is taking for performing the writes. 正在执行类似的方法来执行写操作。 There is some small discrepancy between reads and writes in that for a read of multiple data objects I build one IntermediateReadResult for every data container, while for writes I have only one IntermediateWriteData . 读取和写入之间存在一些细微的差异,对于读取多个数据对象,我为每个数据容器构建一个IntermediateReadResult ,而对于写入,我只有一个IntermediateWriteData This is not clearly visible from the small example below, but can be figured because only the type is passed (as generic argument) onto the Apply<TValue> method. 这在下面的小示例中看不到,但是可以理解,因为仅将类型(作为通用参数)传递给Apply<TValue>方法。

The effect of the explained structure is that now I can have the following set of implementation classes for a PLC type to implement data reading and writing using PlcValueContainer s: 解释的结构的效果是,现在我可以为PLC类型提供以下一组实现类,以使用PlcValueContainer数据读取和写入:

  • BrandDataConverter
  • BrandIntermediateReadResult
  • BrandIntermediateWriteData
  • BrandPlc

There might be additional classes necessary depending on the PLC supplier's API, but those are always required of course. 根据PLC供应商的API,可能还需要其他类,但是这些当然总是必需的。 All the Brand classes live in their own BrandPlc assembly so I don't create dependencies on all PLC brands if I need only one (or two). 所有品牌类都生活在自己的BrandPlc程序BrandPlc因此如果我只需要一个(或两个),就不会对所有PLC品牌都建立依赖关系。 I also have some wrapping code and an abstract PLC class to hide internal API from consumer classes (I don't want the consumers to apply some CustomIntermediateReadResult by accident), but other than that the following code example covers most of what is required: 我也有一些包装代码和一个抽象的PLC类,以从使用者类中隐藏内部API(我不希望使用者偶然地应用一些CustomIntermediateReadResult ),但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));
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM