【问题标题】:C# : Converting Base Class to Child ClassC#:将基类转换为子类
【发布时间】:2026-01-30 14:35:01
【问题描述】:

我有一个类,NetworkClient 作为基类:

using System.IO;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace Network
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

public class NetworkClient
{
    public NetworkClient()
    {
        tcpClient = new TcpClient();
    }
    public NetworkClient(TcpClient client)
    {
        tcpClient = client;
    }

    public virtual bool IsConnected
    {
        get;
        private set;
    }
    private StreamWriter writer { get; set; }
    private StreamReader reader { get; set; }

    private TcpClient tcpClient
    {
        get;
        set;
    }

    public virtual NetworkServerInfo NetworkServerInfo
    {
        get;
        set;
    }

    public async virtual void Connect(NetworkServerInfo info)
    {
        if (tcpClient == null)
        {
            tcpClient=new TcpClient();
        }
        await tcpClient.ConnectAsync(info.Address,info.Port);
        reader = new StreamReader(tcpClient.GetStream());
        writer = new StreamWriter(tcpClient.GetStream());
    }

    public virtual void Disconnect()
    {
        tcpClient.Close();            
        reader.Dispose();

        writer.Dispose();
    }

    public async virtual void Send(string data)
    {
        await writer.WriteLineAsync(data);
    }

    public async virtual Task<string> Receive()
    {
        return await reader.ReadLineAsync();
    }

}
}

还有一个从 NetworkClient 派生的子类:

using System.Net;

namespace Network
{
using Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

public class SkyfilterClient : NetworkClient
{
    public virtual IPAddress Address
    {
        get;
        set;
    }

    public virtual int Port
    {
        get;
        set;
    }

    public virtual string SessionID
    {
        get;
        set;
    }

    public virtual User UserData
    {
        get;
        set;
    }

    protected virtual bool Authenticate(string username, string password)
    {
        throw new System.NotImplementedException();
    }

}
}

问题是,当我尝试将 NetworkClient 转换为 SkyfilterClient 时。抛出异常,无法将“Network.NetworkClient”类型的对象转换为“Network.SkyfilterClient”类型。

我的代码有什么问题?我看到Stream可以转换为NetworkStream,MemoryStream。为什么 NetworkClient 不能转为 Skyfilter Client?

【问题讨论】:

    标签: c# oop class


    【解决方案1】:

    如果您必须这样做,并且您不介意 hack,您可以让序列化为您完成这项工作。

    鉴于这些类:

    public class ParentObj
    {
        public string Name { get; set; }
    }
    
    public class ChildObj : ParentObj
    {
        public string Value { get; set; }
    }
    

    您可以像这样从父实例创建子实例:

    var parent = new ParentObj() { Name = "something" };
    var serialized = JsonConvert.SerializeObject(parent);
    var child = JsonConvert.DeserializeObject<ChildObj>(serialized);
    

    这假设您的对象可以很好地进行序列化,obv。

    请注意,这可能会比显式转换器慢。

    【讨论】:

    • 这很好用,太吓人了。我用它做一些调试,非常有帮助。
    • 为了避免第三方库,json(反)序列化在 System.Web.Script.Serialization 中,请参阅 JavaScriptSerializer
    • 我从不相信黑客
    • 这是一个 hack,但它不是一个 hack……除了它是一个 hack,但它不是真正的 hack。说实话,有时 JSON 序列化/反序列化在一些关键的边缘情况下确实有帮助,在这些情况下,用 C# 做事情的“自然方式”要复杂得多。对于那些边缘情况(包括这个),没关系 - 只要代码实际上需要开始执行类似的操作。
    【解决方案2】:

    有几种方法可以做到这一点。但是,这是执行此操作的最简单方法之一,并且可以重复使用。

    我们正在获取父类的所有属性并更新子类的相同属性。其中 baseObj 是父对象,T 是子类。

    public static T ConvertToDerived<T>(object baseObj) where T : new()
        {
            var derivedObj = new T();
            var members = baseObj.GetType().GetMembers();
            foreach (var member in members)
            {
                object val = null;
                if (member.MemberType == MemberTypes.Field)
                {
                    val = ((FieldInfo)member).GetValue(baseObj);
                    ((FieldInfo)member).SetValue(derivedObj, val);
                }
                else if (member.MemberType == MemberTypes.Property)
                {
                    val = ((PropertyInfo)member).GetValue(baseObj);
                    if (val is IList && val.GetType().IsGenericType)
                    {
                        var listType = val.GetType().GetGenericArguments().Single();
                        var list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(listType));
                        foreach (var item in (IList)val)
                        {
                            list.Add(item);
                        }
                        ((PropertyInfo)member).SetValue(baseObj, list, null);
                    }
                    if (((PropertyInfo)member).CanWrite)
                        ((PropertyInfo)member).SetValue(derivedObj, val);
                }
            }
            return derivedObj;
        }
    

    【讨论】:

    • 它在某种程度上有效,但它不适用于 IList 字段,因为它们都以 0 计数进行转换。
    • @AmirHajiha 我已经修改了代码以帮助处理 IList 字段。
    【解决方案3】:

    我不认为您可以向下转换对象,但是有一种简单的方法可以在框外“向下转换”对象。它不是类型安全的,但它可以工作。先将对象序列化成json,再反序列化成子类对象。它的工作原理与您在 api 之间传递对象一样。所以,虽然有些人可能会说“这不起作用或不好”,但我认为这正是我们的互联网目前的工作方式,所以......为什么不使用这种方法呢?只要参数名称相同,就不需要映射,如果它是子类,它们将是相同的。注意:这可能不会复制任何私有字段;如果您有带参数的构造函数,则可能还需要对其进行测试以确保没有副作用。

    这是我的工具箱:

    public static string ConvertToJson<T>(this T obj)
    {
        return JsonConvert.SerializeObject(obj);
    }
    public static T ConvertToObject<T>(this string json)
    {
        if (string.IsNullOrEmpty(json))
        {
            return Activator.CreateInstance<T>();
        }
        return JsonConvert.DeserializeObject<T>(json);
    }
    

    使用方法如下:

    var sfcl = networkClient.ConvertToJson().ConvertToObject<SkyfilterClient>();
    

    【讨论】:

      【解决方案4】:

      我很惊讶 AutoMapper 没有给出答案。

      从之前的所有答案中都可以清楚地看出,您不能进行类型转换。但是,使用AutoMapper,只需几行代码,您就可以在现有NetworkClient 的基础上实例化一个新的SkyfilterClient

      本质上,您可以将以下内容放在当前进行类型转换的地方:

      using AutoMapper;
      ...
      // somewhere, your network client was declared
      var existingNetworkClient = new NetworkClient();
      ...
      // now we want to type-cast, but we can't, so we instantiate using AutoMapper
      AutoMapper.Mapper.CreateMap<NetworkClient, SkyfilterClient>();
      var skyfilterObject = AutoMapper.Mapper.Map<SkyfilterClient>(existingNetworkClient);
      

      这是一个完整的例子:

        public class Vehicle
        {
          public int NumWheels { get; set; }
          public bool HasMotor { get; set; }
        }
      
        public class Car: Vehicle
        {
          public string Color { get; set; }
          public string SteeringColumnStyle { get; set; }
        }
      
        public class CarMaker
        {
          // I am given vehicles that I want to turn into cars...
          public List<Car> Convert(List<Vehicle> vehicles)
          {
            var cars = new List<Car>();
            AutoMapper.Mapper.CreateMap<Vehicle, Car>(); // Declare that we want some automagic to happen
            foreach (var vehicle in vehicles)
            {
              var car = AutoMapper.Mapper.Map<Car>(vehicle);
              // At this point, the car-specific properties (Color and SteeringColumnStyle) are null, because there are no properties in the Vehicle object to map from.
              // However, car's NumWheels and HasMotor properties which exist due to inheritance, are populated by AutoMapper.
              cars.Add(car);
            }
            return cars;
          }
        }
      

      【讨论】:

      • 除了良好的健康多态性理论之外,很高兴看到有人承认在实践中有时必须编写这样的代码。拥有一个出色的工具不仅可以节省工作量,还可以防止错误!
      • Automapper 是个好工具,谢谢提醒!
      • CreateMap 已被弃用。有关 AutoMapper 的新用法,请参阅本文。 *.com/questions/38194012/…
      【解决方案5】:

      我建议从任何子类中确定您需要的功能,并创建一个通用方法来转换为正确的子类。

      我遇到了同样的问题,但真的不想创建一些映射类或导入库。

      假设您需要 'Authenticate' 方法来获取正确子类的行为。在您的 NetworkClient 中:

      protected bool Authenticate(string username, string password) {
        //...
      }
      protected bool DoAuthenticate<T>(NetworkClient nc, string username, string password) where T : NetworkClient {
      //Do a cast into the sub class.
        T subInst = (T) nc;
        return nc.Authenticate(username, password);
      }
      

      【讨论】:

        【解决方案6】:

        只要对象实际上是SkyfilterClient,那么强制转换就可以工作。这是一个人为的例子来证明这一点:

        using System;
        
        class Program
        {
            static void Main()
            {
                NetworkClient net = new SkyfilterClient();
                var sky = (SkyfilterClient)net;
            }
        }
        
        public class NetworkClient{}
        public class SkyfilterClient : NetworkClient{}
        

        但是,如果它实际上是一个NetworkClient,那么你不能神奇地使它成为子类。这是一个例子:

        using System;
        
        class Program
        {
            static void Main()
            {
                NetworkClient net = new NetworkClient();
                var sky = (SkyfilterClient)net;
            }
        }
        
        public class NetworkClient{}
        public class SkyfilterClient : NetworkClient{}
        

        但是,您可以创建一个转换器类。这也是一个例子:

        using System;
        
        class Program
        {
            static void Main()
            {
                NetworkClient net = new NetworkClient();
                var sky = SkyFilterClient.CopyToSkyfilterClient(net);
            }
        }
        
        public class NetworkClient
        {  
          public int SomeVal {get;set;}
        }
        
        public class SkyfilterClient : NetworkClient
        {
            public int NewSomeVal {get;set;}
            public static SkyfilterClient CopyToSkyfilterClient(NetworkClient networkClient)
            {
                return new SkyfilterClient{NewSomeVal = networkClient.SomeVal};
            }
        }
        

        但是,请记住,您不能以这种方式转换是有原因的。您可能缺少子类所需的关键信息。

        最后,如果你只是想看看尝试的演员阵容是否有效,那么你可以使用is

        if(client is SkyfilterClient)
            cast
        

        【讨论】:

        • 您好,我尝试了您的示例并进行了一些更改。它确实将父对象转换为子对象 - 但它没有将任何父属性迁移到子对象。对于我的代码,我期待一种将父对象转换为子对象并迁移父属性(子属性将为空)的方法。有什么想法吗?
        • @HemantTank 你最好的办法是诚实地提出一个单独的问题。我一直没有维护这些旧问题中的一些。
        • 谢谢。现在我正在使用基于 JSON 的装箱/拆箱技术。不完美但适合我的目的 - *.com/questions/8329470/…
        • 不要忘记,从 C# 7.0 开始,您可以这样做 if(client is SkyfilterClient filterClient) { // use filterClient }
        【解决方案7】:

        您可以使用 as 运算符在兼容的引用类型或可空类型之间执行某些类型的转换。

        SkyfilterClient c = client as SkyfilterClient;
        if (c != null)
        {
            //do something with it
        }
        
        
        
        NetworkClient c = new SkyfilterClient() as NetworkClient; // c is not null
        SkyfilterClient c2 = new NetworkClient() as SkyfilterClient; // c2 is null
        

        【讨论】:

          【解决方案8】:

          您可以将父类的值复制到子类。例如,如果是这种情况,您可以使用反射。

          【讨论】:

            【解决方案9】:

            你不能downcast。如果创建了父对象,则不能将其强制转换为子对象。

            一个建议的解决方法是创建一个父级实现的interface。如果需要,让孩子覆盖功能,或者只公开父母的功能。将强制转换为接口并执行操作。

            编辑:也可以使用is关键字检查对象是否为SkyfilterClient

               if(networkClient is SkyfilterClient)
               {
            
               }
            

            【讨论】:

              【解决方案10】:

              使用强制转换运算符,例如:

              var skyfilterClient = (SkyfilterClient)networkClient;
              

              【讨论】:

              • 我已经这样做了,当我做 Authenticate((SkyfilterClient)client) 时出现错误
              • 我已经这样做了,当我做 Authenticate((SkyfilterClient)client) 时出现错误
              • 在这种情况下,您的 client 实际上可能不是 SkyfilterClient
              【解决方案11】:

              在 OOP 中,您不能将父类的实例强制转换为子类。您只能将子实例转换为它继承自的父实例。

              【讨论】: