如何在C#中为泛型参数分配一个值?

huangapple go评论71阅读模式
英文:

How can I assign a value to a generic parameter in C#?

问题

以下是要翻译的代码部分:

我想编写代码,用于在数据流中读取和写入数据以进行数据包序列化。

分类数据类型,通过Bitconverter编写和读取数据

我将其简单地编写如下。

public int WritePrimitiveType(ArraySegment<byte> stream, int offset, object data)
{
    string data_type = data.GetType().Name;
    switch (data_type)
    {
        case "Boolean":
            Array.Copy(BitConverter.GetBytes((Boolean)data), 0, stream.Array, stream.Offset + offset, sizeof(Boolean));
            return sizeof(Boolean);
        case "Byte":
            Array.Copy(BitConverter.GetBytes((Byte)data), 0, stream.Array, stream.Offset + offset, sizeof(Byte));
            return sizeof(Byte);       
        case "Int32":
            Array.Copy(BitConverter.GetBytes((Int32)data), 0, stream.Array, stream.Offset + offset, sizeof(Int32));
            return sizeof(Int32);        
        default:
            return 0;
    }
}

public int ReadPrimitiveType<T>(ArraySegment<byte> stream, int offset, ref T data)
{
    switch (typeof(T).Name)
    {
        case "Boolean":
            data = (T)((object)BitConverter.ToBoolean(stream.Array, stream.Offset + offset));
            return sizeof(Boolean);
        case "Byte":
            data = (T)((object)BitConverter.ToInt16(stream.Array, stream.Offset + offset));
            return sizeof(Byte);       
        case "Int32":
            data = (T)((object)BitConverter.ToInt32(stream.Array, stream.Offset + offset));
            return sizeof(Int32);
        default:
            data = default(T);
            return 0;
    }
}

如果您想改进上述代码,可以考虑使用泛型来减少重复性,这样可以更通用地处理不同的数据类型。另外,您还可以添加错误处理和验证,以提高代码的健壮性。

英文:

I want to write code that reads and writes data to a stream for packet serialization.

Classify data types and write and read data through Bitconverter.

I wrote this simply as below.

public int WritePrimitiveType(ArraySegment&lt;byte&gt; stream, int offset, object data)
{
    string data_type = data.GetType().Name;
    switch (data_type)
    {
        case &quot;Boolean&quot;:
            Array.Copy(BitConverter.GetBytes((Boolean)data), 0, stream.Array, stream.Offset + offset, sizeof(Boolean));
            return sizeof(Boolean);
        case &quot;Byte&quot;:
            Array.Copy(BitConverter.GetBytes((Byte)data), 0, stream.Array, stream.Offset + offset, sizeof(Byte));
            return sizeof(Byte);       
        case &quot;Int32&quot;:
            Array.Copy(BitConverter.GetBytes((Int32)data), 0, stream.Array, stream.Offset + offset, sizeof(Int32));
            return sizeof(Int32);        
        default:
            return 0;
    }
}


public int ReadPrimitiveType&lt;T&gt;(ArraySegment&lt;byte&gt; stream, int offset, ref T data)
{
    switch (typeof(T).Name)
    {
        case &quot;Boolean&quot;:
            data = (T)((object)BitConverter.ToBoolean(stream.Array, stream.Offset + offset));
            return sizeof(Boolean);
        case &quot;Byte&quot;:
            data = (T)((object)BitConverter.ToInt16(stream.Array, stream.Offset + offset));
            return sizeof(Byte);       
        case &quot;Int32&quot;:
            data = (T)((object)BitConverter.ToInt32(stream.Array, stream.Offset + offset));
            return sizeof(Int32);
        default:
            data = default(T);
            return 0;
    }
}

It appears to have been written in an immature way.

How to properly write such code in C#? I would like to improve the above code.

答案1

得分: 2

以下是已翻译的内容:

The simple way is to forget about using generics at all. Just write the 6 functions with the same names and let the C# compiler work out which one to call based on the provided data argument;

public int WritePrimitiveType(ArraySegment<byte> stream, int offset, bool data){ ... }
public int WritePrimitiveType(ArraySegment<byte> stream, int offset, byte data){ ... }
public int WritePrimitiveType(ArraySegment<byte> stream, int offset, int data){ ... }
public int ReadPrimitiveType(ArraySegment<byte> stream, int offset, ref bool data){ ... }
public int ReadPrimitiveType(ArraySegment<byte> stream, int offset, ref byte data){ ... }
public int ReadPrimitiveType(ArraySegment<byte> stream, int offset, ref int data){ ... }

If you really want to use generics, you could use generic delegates to call the type-specific BitConverter methods.

public static class Converter<T>{
    private static Func<byte[], int, T> reader;
    private static Action<byte[], int, T> writer;
    static Converter(){
        if (typeof(T) == typeof(bool)){
            reader =  (Func<byte[], int, T>)(object) new Func<byte[], int, bool>(BitConverter.ToBoolean);
            writer = ...
        }
        ... etc
    }
    public static int ReadPrimitiveType(ArraySegment<byte> stream, int offset, ref T data){
        data = reader(stream.Array, stream.Offset + offset);
        return sizeof(T);
    }

}

At least this will avoid boxing and unboxing on every read.

英文:

The simple way is to forget about using generics at all. Just write the 6 functions with the same names and let the C# compiler work out which one to call based on the provided data argument;

public int WritePrimitiveType(ArraySegment&lt;byte&gt; stream, int offset, bool data){ ... }
public int WritePrimitiveType(ArraySegment&lt;byte&gt; stream, int offset, byte data){ ... }
public int WritePrimitiveType(ArraySegment&lt;byte&gt; stream, int offset, int data){ ... }
public int ReadPrimitiveType(ArraySegment&lt;byte&gt; stream, int offset, ref bool data){ ... }
public int ReadPrimitiveType(ArraySegment&lt;byte&gt; stream, int offset, ref byte data){ ... }
public int ReadPrimitiveType(ArraySegment&lt;byte&gt; stream, int offset, ref int data){ ... }

If you really want to use generics, you could use generic delegates to call the type specific BitConverter methods.

public static class Converter&lt;T&gt;{
    private static Func&lt;byte[], int, T&gt; reader;
    private static Action&lt;byte[], int, T&gt; writer;
    static Converter(){
        if (typeof(T) == typeof(bool)){
            reader =  (Func&lt;byte[], int, T&gt;)(object) new Func&lt;byte[], int, bool&gt;(BitConverter.ToBoolean);
            writer = ...
        }
        ... etc
    }
    public static int ReadPrimitiveType(ArraySegment&lt;byte&gt; stream, int offset, ref T data){
        data = reader(stream.Array, stream.Offset + offset);
        return sizeof(T);
    }

}

At least this will avoid boxing and unboxing on every read.

答案2

得分: 1

以下是您要翻译的内容:

"你可以将代码泛化,并用Dictionary或其他数据结构替换switch语句。这将大大简化情况,但可能会使代码变慢,而且在某些情况下可能不够可读。

在这里,我们引入了特殊的ProcessingData类来存储每种类型的转换函数。然后,我们用这种类型的实例填充了Dictionary - 对于我们想要读取或写入的每种目标类型,都有一个实例。

现在,我们只需进行字典查找并使用给定的ProcessingData实例来处理给定的数据。

private static readonly Dictionary<Type, ProcessingData> Processors = new()
{
    [typeof(bool)] = new
    (
        readFunc: (s, o) => BitConverter.ToBoolean(s, o),
        writeFunc: d => BitConverter.GetBytes((bool)d),
        size: sizeof(bool)
    ),
    [typeof(byte)] = new
    (
        readFunc: (s, o) => BitConverter.ToBoolean(s, o),
        writeFunc: d => new[] { (byte)d },
        size: sizeof(byte)
    ),
    [typeof(int)] = new
    (
        readFunc: (s, o) => BitConverter.ToInt32(s, o),
        writeFunc: d => BitConverter.GetBytes((int)d),
        size: sizeof(int)
    ),
};

public int WritePrimitiveType(ArraySegment<byte> stream, int offset, object data)
{
    if (Processors.TryGetValue(data.GetType(), out var processor))
    {
        return processor.Write(stream, offset, data);
    }
    else
    {
        return 0;
    }
}

public int ReadPrimitiveType<T>(ArraySegment<byte> stream, int offset, ref T? data)
{
    if (Processors.TryGetValue(typeof(T), out var processor))
    {
        return processor.Read(stream, offset, out data);
    }
    else
    {
        data = default;
        return 0;
    }
}

internal class ProcessingData
{
    private readonly Func<byte[], int, object> _readFunc;
    private readonly Func<object, byte[]> _writeFunc;
    private readonly int _size;

    public ProcessingData(Func<byte[], int, object> readFunc,
                          Func<object, byte[]> writeFunc, int size)
    {
        _readFunc = readFunc;
        _writeFunc = writeFunc;
        _size = size;
    }

    public int Read<T>(ArraySegment<byte> stream, int offset, out T data)
    {
        data = (T)_readFunc(stream.Array, stream.Offset + offset);
        return _size;
    }
    public int Write(ArraySegment<byte> stream, int offset, object data)
    {
        Array.Copy(_writeFunc(data), 0, stream.Array, stream.Offset + offset, _size);
        return _size;
    }
}

您可以尝试使用泛型来从ProcessingData中的基于object的代码转换为泛型代码。这应该显着改善字典初始化代码的可读性,因为您将能够只写BitConverter.ToBoolean(方法组),而不是(s, o) => BitConverter.ToBoolean(s, o) lambda。但我不确定这样的基于泛型的方法是否可行。"

英文:

You can generalize your code and replace the switch statement with the Dictionary or other data structure. This will simplify the situation a lot, but may make the code slower and in some cases less readable.

Here we have introduced the the special ProcessingData class to store conversion functions for each type. Then we filled the Dictionary with instances of such a type - one instance for each target type we want to read or write.

And now we can just do a dictionary lookup and use the given instance of ProcessingData to process the given data.

private static readonly Dictionary&lt;Type, ProcessingData&gt; Processors = new()
{
[typeof(bool)] = new
(
readFunc: (s, o) =&gt; BitConverter.ToBoolean(s, o),
writeFunc: d =&gt; BitConverter.GetBytes((bool)d),
size: sizeof(bool)
),
[typeof(byte)] = new
(
readFunc: (s, o) =&gt; BitConverter.ToBoolean(s, o),
writeFunc: d =&gt; new[] { (byte)d },
size: sizeof(byte)
),
[typeof(int)] = new
(
readFunc: (s, o) =&gt; BitConverter.ToInt32(s, o),
writeFunc: d =&gt; BitConverter.GetBytes((int)d),
size: sizeof(int)
),
};
public int WritePrimitiveType(ArraySegment&lt;byte&gt; stream, int offset, object data)
{
if (Processors.TryGetValue(data.GetType(), out var processor))
{
return processor.Write(stream, offset, data);
}
else
{
return 0;
}
}
public int ReadPrimitiveType&lt;T&gt;(ArraySegment&lt;byte&gt; stream, int offset, ref T? data)
{
if (Processors.TryGetValue(typeof(T), out var processor))
{
return processor.Read(stream, offset, out data);
}
else
{
data = default;
return 0;
}
}
internal class ProcessingData
{
private readonly Func&lt;byte[], int, object&gt; _readFunc;
private readonly Func&lt;object, byte[]&gt; _writeFunc;
private readonly int _size;
public ProcessingData(Func&lt;byte[], int, object&gt; readFunc,
Func&lt;object, byte[]&gt; writeFunc, int size)
{
_readFunc = readFunc;
_writeFunc = writeFunc;
_size = size;
}
public int Read&lt;T&gt;(ArraySegment&lt;byte&gt; stream, int offset, out T data)
{
data = (T)_readFunc(stream.Array, stream.Offset + offset);
return _size;
}
public int Write(ArraySegment&lt;byte&gt; stream, int offset, object data)
{
Array.Copy(_writeFunc(data), 0, stream.Array, stream.Offset + offset, _size);
return _size;
}
}

You can try to play with generics to go from the object-based code in the ProcessingData to generic one. And this should significantly improve the readability of the dictionary initialization code because you will be able to write just BitConverter.ToBoolean (method group) instead of (s, o) =&gt; BitConverter.ToBoolean(s, o) labmda. But I'm not sure if such a generics-based approach is possible.

huangapple
  • 本文由 发表于 2023年5月25日 08:07:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/76328112.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定