68

我有一个类 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?

4

11 回答 11

74

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

using System;

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

public class NetworkClient{}
public class SkyfilterClient : NetworkClient{}

但是,如果它实际上是 a 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
于 2013-05-14T02:47:07.477 回答
46

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

从所有先前的答案中可以清楚地看出,您不能进行类型转换。但是,使用AutoMapper只需几行代码,您就可以SkyfilterClient基于现有的NetworkClient.

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

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;
    }
  }
于 2014-09-03T21:29:29.567 回答
19

如果您必须这样做,并且您不介意 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。

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

于 2018-04-27T14:05:41.473 回答
12

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

于 2013-05-14T02:42:58.097 回答
5

我不认为您可以向下转换对象,但是有一种简单的方法可以在框外“向下转换”对象。它不是类型安全的,但它可以工作。先将对象序列化成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>();
于 2020-08-10T18:47:01.113 回答
5

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

正在发生的事情是我们正在获取父类的所有属性并更新子类上的相同属性。其中 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;
    }
于 2020-04-15T20:57:55.123 回答
4

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

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

编辑:也可以检查对象是否是SkyfilterClientusingis关键字

   if(networkClient is SkyfilterClient)
   {

   }
于 2013-05-14T02:44:10.397 回答
2

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

于 2013-05-14T03:04:46.400 回答
0

您可以使用 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
于 2013-05-14T03:07:17.260 回答
0

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

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

假设您需要 '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);
}
于 2017-02-13T00:28:42.337 回答
-2

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

var skyfilterClient = (SkyfilterClient)networkClient;
于 2013-05-14T02:43:18.427 回答