回答:
C#でダウンキャストする簡単な方法は、親をシリアル化してから、子に逆シリアル化することです。
var serializedParent = JsonConvert.SerializeObject(parentInstance);
Child c = JsonConvert.DeserializeObject<Child>(serializedParent);
上記の2行のコードを使用して、動物を犬にキャストするシンプルなコンソールアプリがあります。
哺乳類を犬にキャストすることはできません-それは猫かもしれません。
サンドイッチに食べ物をキャストすることはできません-それはチーズバーガーかもしれません。
車をフェラーリにキャストすることはできません-それはホンダである可能性があります。より具体的には、フェラーリ360モデナをフェラーリ360チャレンジングストラダーレにキャストすることはできません。
基本クラス参照が参照しているインスタンスは、子クラスのインスタンスではありません。何も問題はありません。
すなわち:
Base derivedInstance = new Derived();
Base baseInstance = new Base();
Derived good = (Derived)derivedInstance; // OK
Derived fail = (Derived)baseInstance; // Throws InvalidCastException
キャストを成功させるには、ダウンキャストするインスタンスが、ダウンキャストするクラスのインスタンスでなければなりません(または、少なくとも、ダウンキャストするクラスはインスタンスのクラス階層内になければなりません)。それ以外の場合は、キャストは失敗します。
このようなキャストが意味をなす場合があります。
私の場合、ネットワークを介してBASEクラスを受け取っていましたが、それに追加の機能が必要でした。だから私が望むすべてのベルとホイッスルで私の側でそれを処理するためにそれを派生させ、受け取ったBASEクラスをDERIVEDクラスにキャストすることは単にオプションではありませんでした(もちろんInvalidCastExceptionをスローします)
すぐに使える実用的な解決策の 1つは、実際にはBASEクラスを継承せず、メンバーとしてITを含めるEXTENSIONヘルパークラスを宣言することでした。
public class BaseExtension
{
Base baseInstance;
public FakeDerived(Base b)
{
baseInstance = b;
}
//Helper methods and extensions to Base class added here
}
あなたは疎結合を持っているだけでなくて、基本クラスへの余分な機能のカップルが必要な場合は本当に導出の絶対的な必要性を持つ、それが迅速かつ簡単な回避策である可能性があります。
BaseExtensionここIBaseであなたが少なくとも同様の文脈でそれを使うことができるようにそれを実装することを望んでいると思いますか?それともあなたのニーズにとって重要ではなかったのですか?
ほとんどの人が、親から子への明示的なキャストは不可能であると言っていますが、実際にはそうではありません。改めてスタートして、例でそれを証明してみましょう。
私たちが.netで知っているように、すべてのキャストには2つの広範なカテゴリがあります。
参照タイプには、さらに3つの主要な状況があり、どのシナリオでも当てはまります。
ケース1.直接または間接の親に対する子
Employee e = new Employee();
Person p = (Person)e; //Allowed
ケース2.親オブジェクトを保持する親変数(許可されていません)
Person p = new Person(); // p is true Person object
Employee e = (Employee)p; //Runtime err : InvalidCastException <-------- Yours issue
ケース3.子オブジェクトを保持する親変数(常に成功)
注:オブジェクトには多態性があるため、親クラス型の変数が子型を保持する可能性があります。
Person p = new Employee(); // p actually is Employee
Employee e = (Employee)p; // Casting allowed
結論:上記を読んだ後、親から子への変換がどのように可能になるか(ケース3)のように理解できることを願っています。
質問への回答:
あなたの答えはケース2です。そのようなキャストがOOPによって許可されておらず、OOPの基本ルールの1つに違反しようとしていることがわかるので、常に安全なパスを選択してください。
さらに、このような例外的な状況を回避するために、.netはis / as演算子を使用することを推奨しています。これは、情報に基づいた決定を行い、安全なキャストを提供するのに役立ちます。
これはオブジェクト指向の原則に違反します。プロジェクトのここや他の場所でのエレガントなソリューションは、AutoMapperのようなオブジェクトマッピングフレームワークを使用してプロジェクションを構成することです。
以下は、必要以上にやや複雑な設定ですが、ほとんどの場合に十分に柔軟です。
public class BaseToChildMappingProfile : Profile
{
public override string ProfileName
{
get { return "BaseToChildMappingProfile"; }
}
protected override void Configure()
{
Mapper.CreateMap<BaseClass, ChildClassOne>();
Mapper.CreateMap<BaseClass, ChildClassTwo>();
}
}
public class AutoMapperConfiguration
{
public static void Configure()
{
Mapper.Initialize(x =>
{
x.AddProfile<BaseToChildMappingProfile>();
});
}
}
アプリケーションが呼び出しAutoMapperConfiguration.Configure()を開始すると、次のように投影できます。
ChildClassOne child = Mapper.Map<BaseClass, ChildClassOne>(baseClass);
プロパティは規則に従ってマッピングされるため、クラスが継承される場合、プロパティ名はまったく同じであり、マッピングは自動的に構成されます。構成を調整することで、追加のプロパティを追加できます。ドキュメントを参照してください。
ポール、あなたは「それをすることはできますか」と尋ねませんでした-私はあなたがそれを行う方法を知りたいと思っています!
プロジェクトでこれを実行する必要がありました-一度だけ一般的な方法でセットアップしたクラスが多く、その後、派生クラスに固有のプロパティを初期化しました。私はVBを使用しているので、サンプルはVBにあります(タフなノギー)が、このサイトからVBサンプルを盗みました。これには、より優れたC#バージョンもあります。
サンプルコード:
Imports System
Imports System.Collections.Generic
Imports System.Reflection
Imports System.Text
Imports System.Diagnostics
Module ClassUtils
Public Sub CopyProperties(ByVal dst As Object, ByVal src As Object)
Dim srcProperties() As PropertyInfo = src.GetType.GetProperties
Dim dstType = dst.GetType
If srcProperties Is Nothing Or dstType.GetProperties Is Nothing Then
Return
End If
For Each srcProperty As PropertyInfo In srcProperties
Dim dstProperty As PropertyInfo = dstType.GetProperty(srcProperty.Name)
If dstProperty IsNot Nothing Then
If dstProperty.PropertyType.IsAssignableFrom(srcProperty.PropertyType) = True Then
dstProperty.SetValue(dst, srcProperty.GetValue(src, Nothing), Nothing)
End If
End If
Next
End Sub
End Module
Module Module1
Class base_class
Dim _bval As Integer
Public Property bval() As Integer
Get
Return _bval
End Get
Set(ByVal value As Integer)
_bval = value
End Set
End Property
End Class
Class derived_class
Inherits base_class
Public _dval As Integer
Public Property dval() As Integer
Get
Return _dval
End Get
Set(ByVal value As Integer)
_dval = value
End Set
End Property
End Class
Sub Main()
' NARROWING CONVERSION TEST
Dim b As New base_class
b.bval = 10
Dim d As derived_class
'd = CType(b, derived_class) ' invalidcast exception
'd = DirectCast(b, derived_class) ' invalidcast exception
'd = TryCast(b, derived_class) ' returns 'nothing' for c
d = New derived_class
CopyProperties(d, b)
d.dval = 20
Console.WriteLine(b.bval)
Console.WriteLine(d.bval)
Console.WriteLine(d.dval)
Console.ReadLine()
End Sub
End Module
もちろん、これは本当にキャストではありません。新しい派生オブジェクトを作成し、親からプロパティをコピーして、子プロパティを空白のままにします。それが私がする必要があるすべてであり、それはあなたがする必要があるすべてのように聞こえます。これは、クラスのメンバー(パブリック変数)ではなく、プロパティのみをコピーします(ただし、パブリックメンバーを公開するのが面倒な場合は、拡張することもできます)。
一般に、キャストすると、同じオブジェクトを指す2つの変数が作成されます(ここでのミニチュートリアル、コーナーケースの例外をスローしないでください)。これには重要な影響があります(読者への運動)。
もちろん、私はなぜその言語がベースからインスタンスを導出することを許さないのか、反対にそうするのかを言わなければなりません。Winformsテキストボックス(派生)のインスタンスを取得して、Winformsコントロール型の変数に格納できる場合を想像してみてください。もちろん、 'control'はオブジェクトをOKの周りに移動でき、テキストボックスに関するすべての 'controll-y'の事柄(たとえば、上、左、.textプロパティ)を処理できます。テキストボックス固有のもの(たとえば、.multiline)は、メモリ内のテキストボックスを指す 'control'タイプの変数をキャストしないと表示できませんが、メモリ内には存在します。
ここで、コントロールがあり、textbox型の変数をそれに制御する場合を想像してください。メモリ内のコントロールには、「マルチライン」やその他のテキストボックスのものがありません。それらを参照しようとすると、コントロールは複数行のプロパティを魔法のように成長させません!プロパティ(ここでは、実際に値を格納するメンバー変数のように見てください。テキストボックスインスタンスのメモリにあるため)が存在する必要があります。キャストしているので、ポイントしているのと同じオブジェクトである必要があります。したがって、それは言語制限ではなく、そのような方法で主張することは哲学的に不可能です。
私の場合は、次のようにすべてのプロパティフィールドを基本クラスから親にコピーするだけで十分です。
using System.Reflection;
public static ChildClass Clone(BaseClass b)
{
ChildClass p = new ChildClass(...);
// Getting properties of base class
PropertyInfo[] properties = typeof(BaseClass).GetProperties();
// Copy all properties to parent class
foreach (PropertyInfo pi in properties)
{
if (pi.CanWrite)
pi.SetValue(p, pi.GetValue(b, null), null);
}
return p;
}
あらゆるオブジェクトの普遍的な解決策はここにあります
C#7.0以降では、isキーワードを使用してこれを行うことができます。
これらのクラスを定義すると:
class Base { /* Define base class */ }
class Derived : Base { /* Define derived class */ }
その後、次のような処理を行うことができます。
void Funtion(Base b)
{
if (b is Derived d)
{
/* Do something with d which is now a variable of type Derived */
}
}
これは次と同等です:
void Funtion(Base b)
{
Defined d;
if (b is Derived)
{
d = (Defined)b;
/* Do something with d */
}
}
あなたは今呼び出すことができます:
Function(new Derived()); // Will execute code defined in if
と同様
Function(new Base()); // Won't execute code defined in if
そうすれば、ダウンキャストが有効で例外がスローされないことを確認できます。
ServiceStackを使用する場合のシリアル化アプローチのバリエーション:
var child = baseObject.ConvertTo<ChildType>();
またはより詳細:
var child = baseObject.ToJson().FromJson<ChildType>();
ServiceStackのシリアライゼーションは非常に高速ですべてかもしれませんが、明らかに、これは低レイテンシ転送での大量変換や、非常に複雑なタイプのソリューションではありません。これはおそらくServiceStackを使用している人には明らかですが、コメントを見込んで明確にすると思います。