C# - 我可以在运行时设置泛型参数的类型来整理以下代码吗?
C# - Can I set the type of a generic parameter at runtime to tidy up the following code?
我有一个下面的例子类
public class Item<TMessageType> where TMessageType : ItemMessage
{
public int Prop1 { get; set; }
public string Prop2 { get; set; }
public int MessageType { get; set; }
public TMessageType Message { get; set; }
}
public class ItemMessage
{
public int SomeProperty { get; set; }
}
public class TypeAMessage: ItemMessage
{
public string PropA { get; set; }
}
public class TypeBMessage: ItemMessage
{
public string PropB { get; set; }
}
public class TypeCMessage: ItemMessage
{
public string PropC { get; set; }
}
我将从 'Items' 作为 JSON 的队列接收来自外部源的消息,并且在收到消息之前不知道项目消息的类型。
我可以通过原始 JSON 字符串上的正则表达式成功确定消息类型,并可以在 switch 语句中使用它来正确反序列化项目。例如
// get type
...
// deserialise
dynamic item;
switch(messageTypeFromJson)
{
case 0:
item = JsonSerializer.Deserialize<Item<TypeAMessage>>(jsonString);
break;
case 1:
item = JsonSerializer.Deserialize<Item<TypeBMessage>>(jsonString);
break;
case 2:
item = JsonSerializer.Deserialize<Item<TypeCMessage>>(jsonString);
break;
default:
// handle unexpected type
return;
}
// use item
以上代码有效,但感觉 很乱。
我希望能够做一些更接近于以下(不起作用)的事情来将类型的确定和反序列化分成不同的步骤。
Type messageType;
switch(messageTypeFromJson)
{
case 0:
messageType = typeof(TypeAMessage);
break;
case 1:
messageType = typeof(TypeBMessage);
break;
case 2:
messageType = typeof(TypeCMessage);
break;
default:
// handle unexpected type
return;
}
try
{
var item = JsonSerializer.Deserialize<Item<messageType>>(jsonString);
.....
}
catch(..){ ... }
有办法实现吗?
在你的 Item<>
class 上没有基础 class 或接口,你能做的最好的事情就是处理 object
。
首先,在 switch 语句中使用完整的类型名称,而不是通用参数:
Type messageType;
switch(messageTypeFromJson)
{
case 0:
messageType = typeof(Item<TypeAMessage>);
break;
case 1:
messageType = typeof(Item<TypeBMessage>);
break;
case 2:
messageType = typeof(Item<TypeCMessage>);
break;
default:
// handle unexpected type
return;
}
然后使用需要对象 Type
:
的 Deserialize 的非泛型重载
var item = JsonSerializer.Deserialize(jsonString, messageType);
现在请务必注意,如果没有基数 type/interface,您就无法将 return 值直接 转换为任何值,将其保留为object
。但是,您可以使用模式匹配来处理对象:
if(item is Item<TypeAMessage> ita)
{
// Do something
}
else if (item is Item<TypeBMessage> itb)
{
// etc
}
或者因为您知道类型(还记得开关吗?),您可以像这样使用它:
switch(messageTypeFromJson)
{
case 0:
Item<TypeAMessage> ita = (Item<TypeAMessage>)item;
// Do something
break;
case 1:
// etc
break;
}
或者,您可以反序列化为 JsonElement
或 JsonDocument
,然后直接使用“原始”JSON 类型。
我会注意到上面的内容非常重复。一种替代方案如下所示:
object item = messageTypeFromJson switch {
0 => (object)JsonSerializer.Deserialize<Item<TypeAMessage>>(jsonString),
1 => JsonSerializer.Deserialize<Item<TypeBMessage>>(jsonString),
2 => JsonSerializer.Deserialize<Item<TypeCMessage>>(jsonString),
_ => null
};
然后你可以打开类型(模式匹配)或 messageTypeFromJson
来决定你想用它做什么。
这完全取决于您打算用 item
做什么——您只是 return 还是需要访问它的属性?如果只是前者,那你就可以走了。如果您需要访问属性,您将需要一个非泛型基类型或接口。
public interface IItem
{
public int Prop1 { get; set; }
public string Prop2 { get; set; }
public int MessageType { get; set; }
public object Message { get; set; }
}
public interface IItem<TMessageType> : IItem where TMessageType : ItemMessage
{
public new TMessageType Message { get; set; }
}
然后:
public class Item<TMessageType> : IItem<TMessageType> where TMessageType : ItemMessage
{
public int Prop1 { get; set; }
public string Prop2 { get; set; }
public int MessageType { get; set; }
object IItem.Message { get => Message; set => Message = value; }
public TMessageType Message { get; set; }
}
然后您就可以像这样使用它了:
var item = (IItem)JsonSerializer.Deserialize(jsonString, messageType);
// Do something with interface properties
或类似的:
IItem item = messageTypeFromJson switch {
0 => (IItem)JsonSerializer.Deserialize<Item<TypeAMessage>>(jsonString),
1 => JsonSerializer.Deserialize<Item<TypeBMessage>>(jsonString),
2 => JsonSerializer.Deserialize<Item<TypeCMessage>>(jsonString),
_ => null
};
然后您可以打开 Message
类型而不是 item
:
if (item.Message is TypeAMessage tam)
{
Console.WriteLine(tam.PropA);
// etc
}
警告:调用Deserialize
时不要使用IItem
作为泛型类型参数(或Type
对象)。为什么?因为 System.Text.Json 只会反序列化该类型上存在的属性。这意味着您将 丢失 强类型 Message
数据和一些 它的 属性。
换句话说,不要这样做:
IItem item = JsonSerializer.Deserialize<IItem>(jsonString);
不推荐使用反射。
代码如下:
//get generic deserialize method (there is two, we should filter to get the one we need.
var deserializeMethod = typeof(JsonConvert).GetMethods().
Where(x =>x.Name == nameof(JsonConvert.DeserializeObject)).
Where(x =>x.IsGenericMethod).FirstOrDefault();
var itemType = typeof(List<>/*item type here*/);
var listGenericType = itemType.MakeGenericType(typeof(int)/*type from switch here*/);
var genericMethod = deserializeMethod.MakeGenericMethod(listGenericType);
var mappedObject = genericMethod.Invoke(null, new object[] { "[1,2,3]" /*your JSON here*/ });
你的方式更易读也更安全。
当使用 GetType() 方法或 typeof() 关键字时,您可以通过这种方式调用通用类型实例和方法。
我有一个下面的例子类
public class Item<TMessageType> where TMessageType : ItemMessage
{
public int Prop1 { get; set; }
public string Prop2 { get; set; }
public int MessageType { get; set; }
public TMessageType Message { get; set; }
}
public class ItemMessage
{
public int SomeProperty { get; set; }
}
public class TypeAMessage: ItemMessage
{
public string PropA { get; set; }
}
public class TypeBMessage: ItemMessage
{
public string PropB { get; set; }
}
public class TypeCMessage: ItemMessage
{
public string PropC { get; set; }
}
我将从 'Items' 作为 JSON 的队列接收来自外部源的消息,并且在收到消息之前不知道项目消息的类型。 我可以通过原始 JSON 字符串上的正则表达式成功确定消息类型,并可以在 switch 语句中使用它来正确反序列化项目。例如
// get type
...
// deserialise
dynamic item;
switch(messageTypeFromJson)
{
case 0:
item = JsonSerializer.Deserialize<Item<TypeAMessage>>(jsonString);
break;
case 1:
item = JsonSerializer.Deserialize<Item<TypeBMessage>>(jsonString);
break;
case 2:
item = JsonSerializer.Deserialize<Item<TypeCMessage>>(jsonString);
break;
default:
// handle unexpected type
return;
}
// use item
以上代码有效,但感觉 很乱。 我希望能够做一些更接近于以下(不起作用)的事情来将类型的确定和反序列化分成不同的步骤。
Type messageType;
switch(messageTypeFromJson)
{
case 0:
messageType = typeof(TypeAMessage);
break;
case 1:
messageType = typeof(TypeBMessage);
break;
case 2:
messageType = typeof(TypeCMessage);
break;
default:
// handle unexpected type
return;
}
try
{
var item = JsonSerializer.Deserialize<Item<messageType>>(jsonString);
.....
}
catch(..){ ... }
有办法实现吗?
在你的 Item<>
class 上没有基础 class 或接口,你能做的最好的事情就是处理 object
。
首先,在 switch 语句中使用完整的类型名称,而不是通用参数:
Type messageType;
switch(messageTypeFromJson)
{
case 0:
messageType = typeof(Item<TypeAMessage>);
break;
case 1:
messageType = typeof(Item<TypeBMessage>);
break;
case 2:
messageType = typeof(Item<TypeCMessage>);
break;
default:
// handle unexpected type
return;
}
然后使用需要对象 Type
:
var item = JsonSerializer.Deserialize(jsonString, messageType);
现在请务必注意,如果没有基数 type/interface,您就无法将 return 值直接 转换为任何值,将其保留为object
。但是,您可以使用模式匹配来处理对象:
if(item is Item<TypeAMessage> ita)
{
// Do something
}
else if (item is Item<TypeBMessage> itb)
{
// etc
}
或者因为您知道类型(还记得开关吗?),您可以像这样使用它:
switch(messageTypeFromJson)
{
case 0:
Item<TypeAMessage> ita = (Item<TypeAMessage>)item;
// Do something
break;
case 1:
// etc
break;
}
或者,您可以反序列化为 JsonElement
或 JsonDocument
,然后直接使用“原始”JSON 类型。
我会注意到上面的内容非常重复。一种替代方案如下所示:
object item = messageTypeFromJson switch {
0 => (object)JsonSerializer.Deserialize<Item<TypeAMessage>>(jsonString),
1 => JsonSerializer.Deserialize<Item<TypeBMessage>>(jsonString),
2 => JsonSerializer.Deserialize<Item<TypeCMessage>>(jsonString),
_ => null
};
然后你可以打开类型(模式匹配)或 messageTypeFromJson
来决定你想用它做什么。
这完全取决于您打算用 item
做什么——您只是 return 还是需要访问它的属性?如果只是前者,那你就可以走了。如果您需要访问属性,您将需要一个非泛型基类型或接口。
public interface IItem
{
public int Prop1 { get; set; }
public string Prop2 { get; set; }
public int MessageType { get; set; }
public object Message { get; set; }
}
public interface IItem<TMessageType> : IItem where TMessageType : ItemMessage
{
public new TMessageType Message { get; set; }
}
然后:
public class Item<TMessageType> : IItem<TMessageType> where TMessageType : ItemMessage
{
public int Prop1 { get; set; }
public string Prop2 { get; set; }
public int MessageType { get; set; }
object IItem.Message { get => Message; set => Message = value; }
public TMessageType Message { get; set; }
}
然后您就可以像这样使用它了:
var item = (IItem)JsonSerializer.Deserialize(jsonString, messageType);
// Do something with interface properties
或类似的:
IItem item = messageTypeFromJson switch {
0 => (IItem)JsonSerializer.Deserialize<Item<TypeAMessage>>(jsonString),
1 => JsonSerializer.Deserialize<Item<TypeBMessage>>(jsonString),
2 => JsonSerializer.Deserialize<Item<TypeCMessage>>(jsonString),
_ => null
};
然后您可以打开 Message
类型而不是 item
:
if (item.Message is TypeAMessage tam)
{
Console.WriteLine(tam.PropA);
// etc
}
警告:调用Deserialize
时不要使用IItem
作为泛型类型参数(或Type
对象)。为什么?因为 System.Text.Json 只会反序列化该类型上存在的属性。这意味着您将 丢失 强类型 Message
数据和一些 它的 属性。
换句话说,不要这样做:
IItem item = JsonSerializer.Deserialize<IItem>(jsonString);
不推荐使用反射。
代码如下:
//get generic deserialize method (there is two, we should filter to get the one we need.
var deserializeMethod = typeof(JsonConvert).GetMethods().
Where(x =>x.Name == nameof(JsonConvert.DeserializeObject)).
Where(x =>x.IsGenericMethod).FirstOrDefault();
var itemType = typeof(List<>/*item type here*/);
var listGenericType = itemType.MakeGenericType(typeof(int)/*type from switch here*/);
var genericMethod = deserializeMethod.MakeGenericMethod(listGenericType);
var mappedObject = genericMethod.Invoke(null, new object[] { "[1,2,3]" /*your JSON here*/ });
你的方式更易读也更安全。
当使用 GetType() 方法或 typeof() 关键字时,您可以通过这种方式调用通用类型实例和方法。