web-dev-qa-db-fra.com

C #: Conversion de la classe de base en classe enfant

J'ai une classe, NetworkClient en tant que classe de base:

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

}
}

Et aussi avoir une classe enfant dérivée de 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();
    }

}
}

Le problème est que, lorsque j'essaie de convertir NetworkClient en SkyfilterClient. Une exception est levée. Impossible de convertir l'objet de type 'Network.NetworkClient' en type 'Network.SkyfilterClient'.

Qu'est ce qui ne va pas avec mon code ? Je vois que Stream peut être converti en NetworkStream, MemoryStream. Pourquoi NetworkClient ne peut pas être converti en client Skyfilter?

36
Aditya Heartly

Tant que l'objet est en fait un SkyfilterClient, alors un casting devrait fonctionner. Voici un exemple artificiel pour le prouver:

using System;

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

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

Cependant, s'il s'agit en fait d'un NetworkClient, vous ne pouvez pas le faire devenir par magie la sous-classe. Voici un exemple de cela:

using System;

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

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

CEPENDANT, vous pouvez créer une classe de convertisseur. Voici un exemple de cela, aussi:

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

Mais gardez à l'esprit qu'il y a une raison pour laquelle vous ne pouvez pas convertir de cette façon. Il se peut que vous manquiez des informations clés dont la sous-classe a besoin.

Enfin, si vous voulez juste voir si la tentative de casting fonctionnera, vous pouvez utiliser is:

if(client is SkyfilterClient)
    cast
44
Justin Pihony

Je suis surpris que AutoMapper ne soit pas une réponse.

Comme cela ressort clairement de toutes les réponses précédentes, vous ne pouvez pas lancer la conversion de type. Cependant, en utilisant AutoMapper , dans quelques lignes de code, vous pouvez avoir un nouveau SkyfilterClient instancié sur la base d'un NetworkClient existant.

Essentiellement, vous placeriez les éléments suivants à l’état actuel de votre conversion:

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

Voici un exemple complet:

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

En programmation orientée objet, vous ne pouvez pas convertir une instance d'une classe parent en une classe enfant. Vous pouvez uniquement convertir une instance enfant en un parent dont elle hérite.

6
Tombala

Si vous devez le faire et que cela ne vous dérange pas, vous pouvez laisser la sérialisation faire le travail à votre place.

Étant donné ces cours:

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

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

Vous pouvez créer une instance enfant à partir d'une instance parent de la manière suivante:

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

Cela suppose que vos objets jouent à Nice avec la sérialisation, obv.

Sachez que cela va probablement être plus lent qu'un convertisseur explicite. Cet exemple seul prend ~ 300ms.

5
Chad Coombs

Vous ne pouvez pas downcast. Si l'objet parent est créé, il ne peut pas être converti en enfant.

Une solution suggérée serait de créer un interface que le parent implémente. Demandez à l'enfant de remplacer la fonctionnalité si nécessaire ou d'exposer simplement la fonctionnalité parents. Changez la distribution pour qu’elle soit une interface et effectuez les opérations.

Edit: Peut-être pourrait-il également vérifier si l'objet est un SkyfilterClient à l'aide de is mot clé

   if(networkClient is SkyfilterClient)
   {

   }
4
jacob aloysious

Vous pouvez copier la valeur de la classe parent dans une classe enfant. Par exemple, vous pouvez utiliser la réflexion si tel est le cas.

2
KrishnaDhungana

Utilisez l'opérateur de casting, en tant que tel:

var skyfilterClient = (SkyfilterClient)networkClient;
0
Stephen Cleary

Vous pouvez utiliser l'opérateur as pour effectuer certains types de conversions entre types de référence compatibles ou types nullables.

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
0
Damith

Je vous recommanderais d'identifier les fonctionnalités dont vous avez besoin à partir de n'importe quelle sous-classe et de créer une méthode générique pour la conversion dans la bonne sous-classe.

J'avais le même problème, mais je n'avais vraiment pas envie de créer une classe de mappage ou d'importer une bibliothèque.

Supposons que vous ayez besoin de la méthode 'Authenticate' pour prendre le comportement de la bonne sous-classe. Dans votre 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);
}
0
balthatrix