You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中含不同类型公共属性的子类设计方案

这个场景太常见了——JSON里同一个字段一会儿是单个对象,一会儿是数组,在C#里确实得花点心思处理。你提到的用子类+抽象类/接口的思路完全可行,甚至是强类型场景下的最优解之一,下面我给你详细拆解几种方案,重点说说你关心的多态实现方式:

方案一:动态类型(快速但不够安全)

如果只是临时读取数据,不想定义太多类,可以用dynamic或者Newtonsoft.Json里的JObject来快速处理,但缺点是编译时没有类型检查,后期维护容易出问题:

using Newtonsoft.Json.Linq;

var json = "你的JSON字符串";
var root = JObject.Parse(json);

foreach (var node in root["myNodes"])
{
    var param3 = node["param3"];
    if (param3.Type == JTokenType.Object)
    {
        // 处理单个对象
        var paramValue = param3["myParam3param"].Value<int>();
        Console.WriteLine($"单个对象值:{paramValue}");
    }
    else if (param3.Type == JTokenType.Array)
    {
        // 处理数组
        foreach (var item in param3)
        {
            var itemA = item["myItemA"].Value<string>();
            Console.WriteLine($"数组项A:{itemA}");
        }
    }
}
方案二:抽象类/接口+自定义JsonConverter(强类型+多态)

这正是你考虑的子类思路,完全可行!抽象类和接口都能实现,下面分别说清楚逻辑:

1. 定义基类/接口与子类

先定义一个抽象基类(或者接口)作为param3的统一类型,再分别实现两种场景的子类:

// 抽象基类,也可以换成接口IParam3
public abstract class Param3Base { }

// 对应单个对象的场景
public class SingleParam3 : Param3Base
{
    public int myParam3param { get; set; }
}

// 数组里的单个项
public class Param3Item
{
    public string myItemA { get; set; }
    public string myItemB { get; set; }
    public string myItemC { get; set; }
}

// 对应数组的场景
public class ArrayParam3 : Param3Base
{
    public List<Param3Item> Items { get; set; }
}

2. 定义根节点与MyNode类

把param3的类型指定为我们的抽象基类,并标记使用自定义转换器:

public class RootObject
{
    public List<MyNode> myNodes { get; set; }
}

public class MyNode
{
    public int param1 { get; set; }
    public string param2 { get; set; }
    [JsonConverter(typeof(Param3Converter))] // 指定自定义转换器
    public Param3Base param3 { get; set; }
}

3. 实现自定义JsonConverter

核心逻辑在这里:判断JSON里的param3是对象还是数组,然后实例化对应的子类:

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

public class Param3Converter : JsonConverter<Param3Base>
{
    public override Param3Base ReadJson(JsonReader reader, Type objectType, Param3Base existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        var token = JToken.Load(reader);
        switch (token.Type)
        {
            case JTokenType.Object:
                // 检查是否有myParam3param字段,确认是SingleParam3类型
                if (token["myParam3param"] != null)
                {
                    return token.ToObject<SingleParam3>();
                }
                break;
            case JTokenType.Array:
                // 数组类型转成ArrayParam3,把元素存入Items列表
                var items = token.ToObject<List<Param3Item>>();
                return new ArrayParam3 { Items = items };
        }
        throw new JsonSerializationException("无法识别的param3类型");
    }

    public override void WriteJson(JsonWriter writer, Param3Base value, JsonSerializer serializer)
    {
        // 如果需要序列化回去,这里也要对应处理不同子类
        if (value is SingleParam3 single)
        {
            serializer.Serialize(writer, single);
        }
        else if (value is ArrayParam3 array)
        {
            serializer.Serialize(writer, array.Items);
        }
        else
        {
            throw new JsonSerializationException("无法序列化的Param3Base子类");
        }
    }
}

4. 使用示例

现在就可以正常反序列化并处理不同类型的param3了:

var json = "{ \"myNodes\": [ { \"param1\": 1, \"param2\": \"myValue2a\", \"param3\": { \"myParam3param\": 0 } }, { \"param1\": 1, \"param2\": \"myValue2b\", \"param3\": [ { \"myItemA\": \"abc\", \"myItemB\": \"def\", \"myItemC\": \"0\" }] }, { \"param1\": 1, \"param2\": \"myValue2c\", \"param3\": [ { \"myItemA\": \"ghi\", \"myItemB\": \"jkl\", \"myItemC\": \"0\" }] }] }";

var root = JsonConvert.DeserializeObject<RootObject>(json);

foreach (var node in root.myNodes)
{
    Console.WriteLine($"param2: {node.param2}");
    if (node.param3 is SingleParam3 single)
    {
        Console.WriteLine($"单个param3值:{single.myParam3param}");
    }
    else if (node.param3 is ArrayParam3 array)
    {
        Console.WriteLine($"数组param3包含{array.Items.Count}个项:");
        foreach (var item in array.Items)
        {
            Console.WriteLine($"  {item.myItemA} | {item.myItemB} | {item.myItemC}");
        }
    }
}
抽象类 vs 接口?

你纠结用抽象类还是接口,其实都可以,看你的需求:

  • 如果Param3Base需要有共享的属性或默认方法(比如一个IsArray()的判断方法),用抽象类更合适,因为它可以提供默认实现。
  • 如果只是想定义一个契约,不需要共享实现逻辑,用接口(比如IParam3)更灵活,因为C#类可以实现多个接口。

两种方式的转换器逻辑基本一致,只是基类的定义形式不同。

额外提示:System.Text.Json的情况

如果你用的是.NET Core/.NET 5+自带的System.Text.Json,思路完全一样,只是自定义转换器的写法略有不同——需要继承JsonConverter<Param3Base>,用Utf8JsonReader和Utf8JsonWriter来处理JSON token,核心的类型判断逻辑不变。

内容的提问来源于stack exchange,提问作者WEFX

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:06:29