如何解决C#中不同输出类型的动态参数列表
我有一个对象,该对象提供了一些功能来写入和读取数据包中的数据,如下所示:
class Packet
{
void Write(int value) {/*...*/}
//...
int ReadInt() {/*...*/}
bool ReadBool() {/*...*/}
string ReadString() {/*...*/}
Vector3 ReadVector3() {/*...*/}
}
此类存储通过网络发送的byte[]
。如果我想访问以前写入的数据,可以这样做
void MyFunction(Packet packet)
{
int myInt = packet.ReadInt();
bool myBool = packet.ReadBool();
string myString = packet.ReadString();
Vector3 myVector3 = packet.ReadVector3();
//... do stuff
}
我想知道是否有某种语法糖可以让我定义一个函数,该函数接受可变数量的不同类型的参数,检测它们在运行时属于哪种动态类型,调用适当的函数,然后返回该参数,像这样:
class Packet
{
//...
void ReadAll(out params object[] objects);
}
void MyFunction(Packet packet)
{
packet.ReadAll(out int myInt,out bool myBool,out string myString,out Vector3 myVector3);
//... do stuff with myInt,myBool,myString,myVector3
}
我用params
,object[]
关键字,泛型和out
看了Convert.ChangeType()
,但到目前为止我什么都没用。我不确定这是否有可能,如果是这样,那么反射的运行时成本是否会大大超过使用与网络数据包一样频繁的代码的简单/更少代码的好处。
谢谢大家。
解决方法
您可以尝试使用泛型和ValueTuples
:
public T Read<T>() where T:ITuple
{
return default(T); // some magic to create and fill one
}
和用法:
var (i,j) = Read<(int,int)>();
// or
var x = Read<(int i,int j)>();
对于反射-您可以按类型缓存反射“结果”:
public T Read<T>() where T : struct,ITuple
{
return TupleCreator<T>.Create(new ValueReader());
}
static class TupleCreator<T> where T : struct,ITuple
{
private static Func<ValueReader,T> factory;
static TupleCreator()
{
var fieldTypes = typeof(T).GetFields()
.Select(fi => fi.FieldType)
.ToArray();
if(fieldTypes.Length > 7)
{
throw new Exception("TODO");
}
var createMethod = typeof(ValueTuple).GetMethods()
.Where(m => m.Name == "Create" && m.GetParameters().Length == fieldTypes.Length)
.SingleOrDefault() ?? throw new NotSupportedException("ValueTuple.Create method not found.");
var createGenericMethod = createMethod.MakeGenericMethod(fieldTypes);
var par = Expression.Parameter(typeof(ValueReader));
// you will need to figure out how to find your `read` methods
// but it should be easy - just find a method starting with "Read"
// And returning desired type
// I can do as simple as:
var readMethod = typeof(ValueReader).GetMethod("Read");
var readCalls = fieldTypes
.Select(t => readMethod.MakeGenericMethod(t)) // can cache here also but I think not needed to
.Select(m => Expression.Call(par,m));
var create = Expression.Call(createGenericMethod,readCalls);
factory = Expression.Lambda<Func<ValueReader,T>>(create,par).Compile();
}
public static T Create(ValueReader reader) => factory(reader);
}
class ValueReader
{
public T Read<T>()
{
return default; // to simulate your read
}
}
Console.WriteLine(Read<(int i,double j)>()); // prints "(0,0)"
NB
您还可以通过here来实现“ Read<T>
”方法的“缓存”。
是的,反射总是要付出性能的代价,但它并不总是那么大,尤其是当您不通过大型集合使用反射调用或处理非常复杂的代码时(here是Microsoft官员关于何时使用反射的教程。
无论如何,回到我们的业务,我认为这可能对您有帮助:
class Packet {
...
public T Read<T>()
{
var currentType = Type.GetType(this.GetType().ToString());
var methodInfo = currentType?
.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance) // Getting all "private" read methods
.FirstOrDefault(m =>
m.Name.Contains("Read") && m.ReturnType == typeof(T));
// here it is assuming that you will have to have only 1 Read method with the type int,bool,etc.
return (T) methodInfo?.Invoke(this,null);
}
}
public List<object> ReadAllAsCollection(params Type[] types)
{
var result = new List<object>(); // in order to hold various type you need to have a collection of `object` type elements
foreach (Type type in types)
{
MethodInfo method = typeof(Packet).GetMethod("Read");
MethodInfo genericMethod = method?.MakeGenericMethod(type);
var res = genericMethod?.Invoke(this,null); // calling Read<T> with appropriate type
result.Add(res);
}
return result;
}
public T ReadAllAsTuple<T>() where T : ITuple
{
T CreateValueTuple(List<object> items,Type[] inputTypes)
{
var methodInfo = typeof(ValueTuple)
.GetMethods()
.FirstOrDefault(m =>
m.Name.Contains("Create") &&
m.GetParameters().Length == items.Count);
var invokeResult = methodInfo?.MakeGenericMethod(inputTypes)
.Invoke(null,items.ToArray());
return (T)invokeResult;
}
var tupleType = typeof(T);
var types = tupleType.GetFields().Select(f => f.FieldType).ToArray();
var result = types
.Select(t =>
{
var method = typeof(Packet).GetMethod("Read");
var genericMethod = method?.MakeGenericMethod(t);
return genericMethod?.Invoke(this,null); // calling Read<T> with appropriate type
}).ToList();
return result.Any() ?
CreateValueTuple(result,types)
: default;
}
和用法:
var p = new Packet();
var elm = p.Read<int>(); // single instance call,ReadInt() call is encapsulated
var resultAsCollection = p.ReadAllAsCollection(typeof(int),typeof(string));
var resultAsTuple = p.ReadAllAsTuple<ValueTuple<int,string,bool>>();
一些注意事项:
- 由于您可能希望使用一个公开的
public
方法(读取),因此最好将其他方法声明为private
。 - 在这种情况下,您具有简单的方法名称,但是要避免可能的名称冲突,可以使用一些特定的命名约定,例如:
_ReadInt_()
或您喜欢的任何其他命名。但是,即使没有特定的命名,此示例也可以使用。 - 假设您只有一个名称为
Read...
且返回类型为T的方法(例如int ReadInt()
),因为在上述情况下,我们仅使用第一个匹配项。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。