C# : Converting Base Class to Child Class

I have a class, NetworkClient as a base class :

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 :

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. An exception is thrown, Unable to cast object of type 'Network.NetworkClient' to type 'Network.SkyfilterClient'.

Whats wrong with my code ? I see that Stream can be converted to NetworkStream, MemoryStream. Why NetworkClient can't be converted to Skyfilter Client?


Solution 1:

As long as the object is actually a SkyfilterClient, then a cast should work. 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. 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:

if(client is SkyfilterClient)
    cast

Solution 2:

I'm surprised AutoMapper hasn't come up as an answer.

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.

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

Solution 3:

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.

Be aware that this is probably going to be slower than an explicit converter.

Solution 4:

In OOP, you can't cast an instance of a parent class into a child class. You can only cast a child instance into a parent that it inherits from.