简体   繁体   English

C#:将基类转换为子类

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

I have a class, NetworkClient as a base 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();
    }

}
}

And also have a child class derived from NetworkClient :还有一个从 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();
    }

}
}

The problem is, that when im trying to cast NetworkClient into SkyfilterClient.问题是,当我尝试将 NetworkClient 转换为 SkyfilterClient 时。 An exception is thrown, Unable to cast object of type 'Network.NetworkClient' to type 'Network.SkyfilterClient'.抛出异常,无法将“Network.NetworkClient”类型的对象转换为“Network.SkyfilterClient”类型。

Whats wrong with my code ?我的代码有什么问题? I see that Stream can be converted to NetworkStream, MemoryStream.我看到Stream可以转换为NetworkStream,MemoryStream。 Why NetworkClient can't be converted to Skyfilter Client?为什么 NetworkClient 无法转换为 Skyfilter Client?

As long as the object is actually a SkyfilterClient , then a cast should work.只要对象实际上是SkyfilterClient ,那么演员表就应该起作用。 Here is a contrived example to prove this:这是一个人为的例子来证明这一点:

using System;

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

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

However, if it is actually a NetworkClient , then you cannot magically make it become the subclass.然而,如果它实际上是一个NetworkClient ,那么你不能神奇地使它成为子类。 Here is an example of that:这是一个例子:

using System;

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

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

HOWEVER, you could create a converter class.但是,您可以创建一个转换器类。 Here is an example of that, also:这是一个例子,还有:

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

But, keep in mind that there is a reason you cannot convert this way.但是,请记住,您无法以这种方式进行转换是有原因的。 You may be missing key information that the subclass needs.您可能缺少子类所需的关键信息。

Finally, if you just want to see if the attempted cast will work, then you can use is :最后,如果您只想查看尝试的转换是否有效,那么您可以使用is

if(client is SkyfilterClient)
    cast

I'm surprised AutoMapper hasn't come up as an answer.我很惊讶 AutoMapper 没有作为答案出现。

As is clear from all the previous answers, you cannot do the typecast.从之前的所有答案中可以清楚地看出,您不能进行类型转换。 However, using AutoMapper , in a few lines of code you can have a new SkyfilterClient instantiated based on an existing NetworkClient .但是,使用AutoMapper ,只需几行代码,您就可以根据现有的NetworkClient实例化一个新的SkyfilterClient

In essence, you would put the following where you are currently doing your typecasting:本质上,您可以将以下内容放在您当前进行类型转换的位置:

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

Here's a full-blown example:这是一个完整的例子:

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

If you HAVE to, and you don't mind a hack, you could let serialization do the work for you.如果您必须这样做,并且您不介意黑客攻击,您可以让序列化为您完成这项工作。

Given these classes:鉴于这些类:

public class ParentObj
{
    public string Name { get; set; }
}

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

You can create a child instance from a parent instance like so:您可以从父实例创建子实例,如下所示:

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

This assumes your objects play nice with serialization, obv.这假设您的对象在序列化方面表现良好,obv。

Be aware that this is probably going to be slower than an explicit converter.请注意,这可能比显式转换器慢。

In OOP, you can't cast an instance of a parent class into a child class.在 OOP 中,您不能将父类的实例转换为子类。 You can only cast a child instance into a parent that it inherits from.您只能将子实例转换为它继承的父实例。

You can't downcast .你不能downcast If the parent object is created, it cannot be cast to the child.如果创建了父对象,则无法将其强制转换为子对象。

One suggested workaround would be to Create an interface which the parent implements.一种建议的解决方法是创建一个父级实现的interface Have the child override functionality if needed or just expose the parents functionality.如果需要,让孩子覆盖功能,或者只公开父母的功能。 Change the cast to be an interface and do the operations.将转换更改为接口并执行操作。

Edit: May be could also check if the object is a SkyfilterClient using is keyword编辑:也可以使用is关键字检查对象是否是SkyfilterClient

   if(networkClient is SkyfilterClient)
   {

   }

There's a few ways of doing this.有几种方法可以做到这一点。 However, here is one of the easiest ways to do this and it's reusable.但是,这是执行此操作的最简单方法之一,并且可以重复使用。

What is happening is that we're getting all the properties of the parent class and updating those same properties on the child class.发生的事情是我们正在获取父类的所有属性并更新子类上的相同属性。 Where baseObj would be the parent object and T would be the child class.其中 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;
    }

I don't think you can downcast an object, however there is a simple way to "downcast" the object outside the box.我不认为你可以向下投射一个对象,但是有一种简单的方法可以将对象“向下投射”到盒子外。 It isn't type safe, but it works.它不是类型安全的,但它可以工作。 First serialize the object into json, then deserialize it into the child class object.先将对象序列化为json,再反序列化为子类对象。 It works the same as if you were passing the object between apis.它的工作原理与您在 apis 之间传递对象一样。 So, while there are some people who may say "this doesn't work or isn't good", I would argue that it is exactly the way our internet currently works, so... why not use that method?所以,虽然有些人可能会说“这行不通或不好”,但我认为这正是我们互联网目前的工作方式,所以......为什么不使用这种方法? No mapping required as long as parameter names are the same, and they will be if it is a child class.只要参数名称相同就不需要映射,如果是子类就可以。 Note: This will likely not copy any private fields;注意:这可能不会复制任何私有字段; if you have a constructor with parameters, this probably needs to be tested as well to ensure there aren't side effects.如果您有一个带参数的构造函数,则可能还需要对其进行测试以确保没有副作用。

Here's my toolbox:这是我的工具箱:

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

Here's how to use it:以下是如何使用它:

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

You can copy value of Parent Class to a Child class.您可以将父类的值复制到子类。 For instance, you could use reflection if that is the case.例如,如果是这种情况,您可以使用反射。

You can use the as operator to perform certain types of conversions between compatible reference types or nullable types.您可以使用 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

I would recommend identifying the functionality you need from any subclasses, and make a generic method to cast into the right subclass.我建议从任何子类中确定您需要的功能,并创建一个通用方法来转换为正确的子类。

I had this same problem, but really didn't feel like creating some mapping class or importing a library.我有同样的问题,但真的不想创建一些映射类或导入库。

Let's say you need the 'Authenticate' method to take behavior from the right subclass.假设您需要 'Authenticate' 方法来获取来自正确子类的行为。 In your NetworkClient:在您的 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