基本クラスとして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クライアントに変換できないのはなぜですか?
オブジェクトが実際に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
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;
}
}
OOPでは、親クラスのインスタンスを子クラスにキャストできません。子インスタンスは、継承元の親にのみキャストできます。
もしあなたがハックに興味があり、あなたがハッキングを気にしないなら、あなたはシリアライゼーションにあなたのための仕事をさせることができます。
これらのクラスを考えます:
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);
これは、オブジェクトがシリアル化で適切に動作することを前提としています。
これはおそらく、明示的なコンバーターよりも遅くなることに注意してください。この例だけでも約300msかかります。
downcast
はできません。親オブジェクトが作成された場合、子にキャストできません。
推奨される回避策の1つは、親が実装するinterface
を作成することです。必要に応じて子に機能をオーバーライドさせるか、単に親の機能を公開します。キャストをインターフェースに変更し、操作を実行します。
編集:オブジェクトがSkyfilterClient
キーワードを使用してis
であるかどうかを確認することもできます
if(networkClient is SkyfilterClient)
{
}
親クラスの値を子クラスにコピーできます。たとえば、その場合はリフレクションを使用できます。
次のようにキャスト演算子を使用します。
var skyfilterClient = (SkyfilterClient)networkClient;
As演算子を使用して、互換性のある参照型またはNULL入力可能な型の間で特定の種類の変換を実行できます。
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
サブクラスから必要な機能を特定し、適切なサブクラスにキャストする汎用メソッドを作成することをお勧めします。
これと同じ問題がありましたが、実際にはマッピングクラスを作成したり、ライブラリをインポートしたりする気はありませんでした。
適切なサブクラスから動作を取得するには、 '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);
}