親クラスから子クラスにキャストできません


97

親クラスから子クラスにキャストしようとしていますが、InvalidCastExceptionが発生します。子クラスには、int型のプロパティが1つだけあります。誰か私が何をする必要があるか知っていますか?


また、基本クラスや派生クラスに明示的なキャストを使用することはできません。
Rzassar 2017

回答:


133

C#でダウンキャストする簡単な方法は、親をシリアル化してから、子に逆シリアル化することです。

 var serializedParent = JsonConvert.SerializeObject(parentInstance); 
 Child c  = JsonConvert.DeserializeObject<Child>(serializedParent);

上記の2行のコードを使用して、動物を犬にキャス​​トするシンプルなコンソールアプリがあります。


19
さて、私はこれを「ダウンキャスト」と呼ぶのをためらいます。
Kirk Woll

ちなみに、変数名は上記と同じではありません。
Jake Gaston、

5
誰かがボックスの外側で考え、OPがそれを行うことができないことを伝える人々を沈黙させるときが大好きです(1つまたは2つのトロールのために保存してください)。これについての援助をありがとう。私は過去数時間、これを理解しようと試みてきました:)
derekmx271 '

3
これは優れたソリューションです。私の子クラスは、追加機能のない親のラッパーにすぎない場合がありました。ヘルパーライブラリにあるため、アプリケーションにWeb参照をインポートする必要はありませんでした。これにより、親をラッパークラスに変換できました。ありがとうございました!
BrianVPS 2017年

1
あなたは天才です!:)
Yablargo

118

哺乳類を犬にキャス​​トすることはできません-それは猫かもしれません。

サンドイッチに食べ物をキャストすることはできません-それはチーズバーガーかもしれません。

車をフェラーリにキャストすることはできません-それはホンダである可能性があります。より具体的には、フェラーリ360モデナをフェラーリ360チャレンジングストラダーレにキャストすることはできません。


17
理解可能な障害、したがって、この方法で実際に「キャスティング」することは不可能です。しかし、哺乳類のオブジェクトに保持されている猫と同じ目の色/体重/髪のパターン/年齢などの犬が欲しい場合はどうでしょうか?基本的に、共通のプロパティをコピーします。
FastAl

7
FastAl、これがまさに私たちがインターフェースを持っている理由です。MammalはIMammalを実装し、目の色、体重などを含める必要があります。これで、犬と猫の両方をIMammalにキャストできます。
トムデロフォード

1
哺乳類を犬にトライキャストできます。犬なら犬です。それ以外の場合はnullになります。「オーバーロード」関数は、猫がこれを可能にするオーバーロード関数を持っている場合、猫から犬への不可能な変換を可能にする可能性があります。しかし、データ損失を処理し、存在しないデータを適応させるのはあなたの仕事です。爪を釘に変えたり、弦を追いかけたり、ボールを追いかけたりするなど
TamusJRoyce

私は例が少し極端で選択的であると思います、そしておそらくキャストはコピーコンストラクタのショートカットです。たとえば、基本オブジェクトcarで定義されたプロパティを使用してフェラーリを構築しています。または、人間から始めて、少年を作成します。キャスティングと直接使用?それはノーノーだと同意する。しかし、それがコンストラクタか何かの一部である場合、動作する可能性があります。以下のシリアル化の答えはいい感じです。
sirthomas 2014

1
フェラーリのアナロジーNICE
ロードダースベーダー

57

基本クラス参照が参照しているインスタンスは、子クラスのインスタンスではありません。何も問題はありません。

すなわち:

Base derivedInstance = new Derived();
Base baseInstance = new Base();

Derived good = (Derived)derivedInstance; // OK
Derived fail = (Derived)baseInstance; // Throws InvalidCastException

キャストを成功させるには、ダウンキャストするインスタンスが、ダウンキャストするクラスのインスタンスでなければなりません(または、少なくとも、ダウンキャストするクラスはインスタンスのクラス階層内になければなりません)。それ以外の場合は、キャストは失敗します。


または潜在的にBase otherDerived = new OtherDerived(); Derived otherFail =(Derived)otherDerived;
ブレアコンラッド

class Base {} class Derived:Base {} //メインメソッド内BasederivedInstance = new Derived(); ベースbaseInstance = new Base(); Derived good =(Derived)derivedInstance; Derived fail =(Derived)baseInstance; これは、.NET 3.5ではエラーなしでコンパイルされます。あなたが言っている問題はどこにありますか?
pradeeptp 2010年

7
@pradeeptp:もちろんビルドします。誰がコンパイルエラーについて何か言いましたか?
Greg D

17

このようなキャストが意味をなす場合があります。
私の場合、ネットワークを介して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であなたが少なくとも同様の文脈でそれを使うことができるようにそれを実装することを望んでいると思いますか?それともあなたのニーズにとって重要ではなかったのですか?
tobriand 2015年

場合によっては、継承を適切に置き換えることができる場合もあります
Vahid Ghadiri、2018

17

ほとんどの人が、親から子への明示的なキャストは不可能であると言っていますが、実際にはそうではありません。改めてスタートして、例でそれを証明してみましょう。

私たちが.netで知っているように、すべてのキャストには2つの広範なカテゴリがあります。

  1. 値タイプ
  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演算子を使用することを推奨しています。これは、情報に基づいた決定を行い、安全なキャストを提供するのに役立ちます。


13

これはオブジェクト指向の原則に違反します。プロジェクトのここや他の場所でのエレガントなソリューションは、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);

プロパティは規則に従ってマッピングされるため、クラスが継承される場合、プロパティ名はまったく同じであり、マッピングは自動的に構成されます。構成を調整することで、追加のプロパティを追加できます。ドキュメントを参照してください。


Automapperを使用して、単一のプロパティを持つタイプを別のタイプに(説明されているOPのように)マップすることは、スレッジハンマーを使用して卵を割るのに似ています。派生型を新しくしてそのプロパティを自分で割り当てないのはなぜですか(1行のコードです)。
bytedev

9

ポール、あなたは「それをすることはできますか」と尋ねませんでした-私はあなたがそれを行う方法を知りたいと思っています!

プロジェクトでこれを実行する必要がありました-一度だけ一般的な方法でセットアップしたクラスが多く、その後、派生クラスに固有のプロパティを初期化しました。私はVBを使用しているので、サンプルはVBにあります(タフなノギー)が、このサイトからVBサンプルを盗みました。これには、より優れたC#バージョンもあります。

http://www.eggheadcafe.com/tutorials/aspnet/a4264125-fcb0-4757-9d78-ff541dfbcb56/net-reflection--copy-cl.aspx

サンプルコード:

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型の変数をそれに制御する場合を想像してください。メモリ内のコントロールには、「マルチライン」やその他のテキストボックスのものがありません。それらを参照しようとすると、コントロールは複数行のプロパティを魔法のように成長させません!プロパティ(ここでは、実際に値を格納するメンバー変数のように見てください。テキストボックスインスタンスのメモリにあるため)存在する必要あります。キャストしているので、ポイントしているのと同じオブジェクトである必要があります。したがって、それは言語制限ではなく、そのような方法で主張することは哲学的に不可能です。


1
私はこれが事実の後の方法であることを知っていますが、 "AndAlso dstProperty.CanWrite"を "If dstProperty IsNot Nothing"テストに含めて、読み取り専用プロパティではないことを確認する必要があります。
JamesMLV 2010

@JamesMLV-良いキャッチに感謝。「事後」-OPがいずれにせよ回答を受け入れるつもりはないようです:-(したがって、事後の事実はありません。まあ
FastAl

4

オブジェクトのインスタンスは、子クラスのタイプを使用して作成する必要があります。親タイプのインスタンスを子タイプにキャストすることはできません


2

私の場合は、次のようにすべてのプロパティフィールドを基本クラスから親にコピーするだけで十分です。

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

あらゆるオブジェクトの普遍的な解決策はここにあります


2

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

そうすれば、ダウンキャストが有効で例外がスローされないことを確認できます。


1

キャストするには、実際のオブジェクトはTypeであるか、またはそれから派生 ている必要がありますあなたにキャストしようとしているタイプ...

または、逆に言えば、キャストしようとしているTypeは、オブジェクトの実際のタイプと同じか、その基本クラスである必要があります。

実際のオブジェクトのタイプがBaseclassの場合、派生クラスType ...にキャストできません。


1

ServiceStackを使用する場合のシリアル化アプローチのバリエーション:

var child = baseObject.ConvertTo<ChildType>();

またはより詳細:

var child = baseObject.ToJson().FromJson<ChildType>();

ServiceStackのシリアライゼーションは非常に高速ですべてかもしれませんが、明らかに、これは低レイテンシ転送での大量変換や、非常に複雑なタイプのソリューションではありません。これはおそらくServiceStackを使用している人には明らかですが、コメントを見込んで明確にすると思います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.