簡體   English   中英

C#:將基類轉換為子類

[英]C# : Converting Base Class to Child Class

我有一個類 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?

只要對象實際上是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

我很驚訝 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;
    }
  }

如果您必須這樣做,並且您不介意黑客攻擊,您可以讓序列化為您完成這項工作。

鑒於這些類:

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。

請注意,這可能比顯式轉換器慢。

在 OOP 中,您不能將父類的實例轉換為子類。 您只能將子實例轉換為它繼承的父實例。

你不能downcast 如果創建了父對象,則無法將其強制轉換為子對象。

一種建議的解決方法是創建一個父級實現的interface 如果需要,讓孩子覆蓋功能,或者只公開父母的功能。 將轉換更改為接口並執行操作。

編輯:也可以使用is關鍵字檢查對象是否是SkyfilterClient

   if(networkClient is SkyfilterClient)
   {

   }

有幾種方法可以做到這一點。 但是,這是執行此操作的最簡單方法之一,並且可以重復使用。

發生的事情是我們正在獲取父類的所有屬性並更新子類上的相同屬性。 其中 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;
    }

我不認為你可以向下投射一個對象,但是有一種簡單的方法可以將對象“向下投射”到盒子外。 它不是類型安全的,但它可以工作。 先將對象序列化為json,再反序列化為子類對象。 它的工作原理與您在 apis 之間傳遞對象一樣。 所以,雖然有些人可能會說“這行不通或不好”,但我認為這正是我們互聯網目前的工作方式,所以......為什么不使用這種方法? 只要參數名稱相同就不需要映射,如果是子類就可以。 注意:這可能不會復制任何私有字段; 如果您有一個帶參數的構造函數,則可能還需要對其進行測試以確保沒有副作用。

這是我的工具箱:

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>();

您可以將父類的值復制到子類。 例如,如果是這種情況,您可以使用反射。

您可以使用 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

我建議從任何子類中確定您需要的功能,並創建一個通用方法來轉換為正確的子類。

我有同樣的問題,但真的不想創建一些映射類或導入庫。

假設您需要 '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);
}

使用強制轉換運算符,如下所示:

var skyfilterClient = (SkyfilterClient)networkClient;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM