C # : 将基类转换为子类

我有一个类,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’。

我的代码有什么问题?我看到记忆流可以转换成网络流,记忆流。为什么 NetworkClient 不能转换为 Skyfilter Client?

146888 次浏览

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

使用强制转换操作符,例如:

var skyfilterClient = (SkyfilterClient)networkClient;

如果创建了父对象,则无法将其强制转换为子对象。

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

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

   if(networkClient is SkyfilterClient)
{


}

只要对象实际上是 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

可以将 Parent Class 的值复制到 Child 类。例如,在这种情况下,您可以使用反射。

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

我很惊讶 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;
}
}

我建议从任何子类中确定所需的功能,并创建一个泛型方法来强制转换到正确的子类中。

我也有同样的问题,但是真的不想创建映射类或者导入库。

假设您需要“ Authenticate”方法来从正确的子类获取行为:

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

如果必须这样做,而且您不介意被黑客攻击,那么您可以让序列化来完成这项工作。

鉴于这些类别:

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

这里假设对象能够很好地进行序列化。

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

有几种方法可以做到这一点。然而,这里有一个最简单的方法来做到这一点,它是可重用的。

我们正在获取父类的所有属性并在子类上更新这些相同的属性。其中 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>();