web-dev-qa-db-ja.com

明示的な型キャストを使用して、基本クラスオブジェクトを派生クラス参照に割り当てることはできますか?

C#で明示的な型キャストを使用して、派生クラス参照に基本クラスオブジェクトを割り当てることは可能ですか?.

私はそれを試してみましたが、実行時エラーが発生します。

73
Maddy.Shik

いいえ。派生クラスへの参照は、実際には派生クラスのインスタンス(またはnull)を参照する必要があります。それ以外の場合、どのように動作することを期待しますか?

例えば:

object o = new object();
string s = (string) o;
int i = s.Length; // What can this sensibly do?

基本型のインスタンスを派生型に変換できるようにする場合は、適切な派生型インスタンスを作成するメソッドを記述することをお勧めします。または、継承ツリーをもう一度見て、最初からこれを行う必要がないように再設計してください。

86
Jon Skeet

いいえ、派生クラス参照に割り当てることは、「基本クラスは派生クラスの完全な代替であり、派生クラスが実行できるすべてのことを実行できます」と言うので、それは不可能です。基本クラスよりも多くの機能(少なくとも、継承の背後にある考え方です)。

パラメーターとして基本クラスオブジェクトを取得し、値をコピーして、派生クラスにコンストラクターを作成できます。

このようなもの:

public class Base {
    public int Data;

    public void DoStuff() {
        // Do stuff with data
    }
}

public class Derived : Base {
    public int OtherData;

    public Derived(Base b) {
        this.Data = b.Data;
        OtherData = 0; // default value
    }

    public void DoOtherStuff() {
        // Do some other stuff
    }
}

その場合、ベースオブジェクトをコピーし、派生メンバーのデフォルト値を備えた完全に機能する派生クラスオブジェクトを取得します。これにより、Jon Skeetが指摘した問題を回避することもできます。

Base b = new Base();
Dervided d = new Derived();

b.DoStuff();    // OK
d.DoStuff();    // Also OK
b.DoOtherStuff();    // Won't work!
d.DoOtherStuff();    // OK

d = new Derived(b);  // Copy construct a Derived with values of b
d.DoOtherStuff();    // Now works!
41
Michael Klement

この問題が発生したため、型パラメーターを受け取り、現在のオブジェクトをその型に変換するメソッドを追加することで解決しました。

public TA As<TA>() where TA : Base
{
    var type = typeof (TA);
    var instance = Activator.CreateInstance(type);

     PropertyInfo[] properties = type.GetProperties();
     foreach (var property in properties)
     {
         property.SetValue(instance, property.GetValue(this, null), null);
     }

     return (TA)instance;
}

つまり、次のようなコードで使用できます。

var base = new Base();
base.Data = 1;
var derived = base.As<Derived>();
Console.Write(derived.Data); // Would output 1

他の多くの人が答えたように、いいえ。

基本型を派生型として使用する必要がある不幸な場合に、次のコードを使用します。はい、それはリスコフ代替原則(LSP)の違反であり、ほとんどの場合、継承よりも合成を優先します。元の答えがこれに基づいているMarkus Knappen Johanssonの小道具。

基本クラスの次のコード:

    public T As<T>()
    {
        var type = typeof(T);
        var instance = Activator.CreateInstance(type);

        if (type.BaseType != null)
        {
            var properties = type.BaseType.GetProperties();
            foreach (var property in properties)
                if (property.CanWrite)
                    property.SetValue(instance, property.GetValue(this, null), null);
        }

        return (T) instance;
    }

許可:

    derivedObject = baseObect.As<derivedType>()

反射を使用するため、「高価」です。適宜使用してください。

10
MEC

いいえ、不可能です。そのため、ランタイムエラーが発生します。

ただし、派生クラスのインスタンスを基本クラス型の変数に割り当てることはできます。

5
ybo

ここで皆が言ったように、それは直接可能ではありません。

私が好み、かなりきれいな方法は、 AutoMapper のようなオブジェクトマッパーを使用することです。

あるインスタンスから別のインスタンスにプロパティをコピーするタスクを実行します(必ずしも同じタイプである必要はありません)。

5
Mahmoodvcs

基本クラスとして型指定された変数を派生クラスの型にキャストできます。ただし、必要に応じて、実行時チェックを行い、実際のオブジェクトが正しいタイプかどうかを確認します。

作成後、オブジェクトのtypeは変更できません(少なくとも、同じサイズではない可能性があります)。ただし、convertインスタンスを作成して、2番目のタイプのnewインスタンスを作成できます-ただし、変換を記述する必要があります手動でコーディングします。

3
Marc Gravell

いいえ、できません。

ACBusがベースクラスバスの派生クラスであるシナリオを考えます。 ACBusには、ACStateという名前のフィールドで動作するTurnOnACやTurnOffACなどの機能があります。 TurnOnACはACStateをオンに設定し、TurnOffACはACStateをオフに設定します。バスでTurnOnACおよびTurnOffAC機能を使用しようとすると、意味がありません。

2
Rohit Dodle
class Program
{
    static void Main(string[] args)
    {
        a a1 = new b();  
        a1.print();  
    }
}
class a
{
    public a()
    {
        Console.WriteLine("base class object initiated");
    }
    public void print()
    {
        Console.WriteLine("base");
    }
}
class b:a
{
    public b()
    {
        Console.WriteLine("child class object");
    }
    public void print1()
    {
        Console.WriteLine("derived");
    }
}

}

子クラスオブジェクトを作成すると、基本クラスオブジェクトが自動的に開始されるため、基本クラス参照変数は子クラスオブジェクトを指すことができます。

ただし、子クラスオブジェクトは作成されないため、子クラス参照変数は基本クラスオブジェクトを指すことができないため、その逆はできません。

また、基本クラス参照変数は基本クラスメンバーのみを呼び出すことができます。

2

実際にはISこれを行う方法です。NewtonsoftJSONを使用してjsonからオブジェクトをデシリアライズする方法を考えてください。不足している要素を無視し、少なくともそれは知っています。

だからここに私がそれをやった方法です。私の説明の後に小さなコードサンプルがあります。

  1. 基本クラスからオブジェクトのインスタンスを作成し、それに応じて入力します。

  2. Newtonsoft jsonの「jsonconvert」クラスを使用して、そのオブジェクトをjson文字列にシリアル化します。

  3. 次に、手順2で作成したjson文字列を使用して逆シリアル化してサブクラスオブジェクトを作成します。これにより、基本クラスのすべてのプロパティを使用してサブクラスのインスタンスが作成されます。

これは魅力のように機能します!だから..これはいつ便利ですか?一部の人々は、これが理にかなっているのかと尋ね、OPのスキーマを変更して、クラス継承(.Netで)をネイティブに実行できないという事実に対応するよう提案しました。

私の場合、サービスのすべての「基本」設定を含む設定クラスがあります。特定のサービスにはより多くのオプションがあり、それらは異なるDBテーブルから取得されるため、これらのクラスは基本クラスを継承します。それらはすべて異なるオプションのセットを持っています。そのため、サービスのデータを取得する場合、最初にベースオブジェクトのインスタンスを使用して値を設定する方がはるかに簡単です。単一のDBクエリでこれを行う1つの方法。その直後に、上記のメソッドを使用してサブクラスオブジェクトを作成します。次に、2番目のクエリを作成し、サブクラスオブジェクトのすべての動的な値を設定します。

最終的な出力は、すべてのオプションが設定された派生クラスです。追加の新しいサブクラスに対してこれを繰り返すには、ほんの数行のコードが必要です。シンプルで、非常に試行錯誤されたパッケージ(Newtonsoft)を使用して、魔法を機能させます。

このサンプルコードはvb.Netですが、C#に簡単に変換できます。

' First, create the base settings object.
    Dim basePMSettngs As gtmaPayMethodSettings = gtmaPayments.getBasePayMethodSetting(payTypeId, account_id)
    Dim basePMSettingsJson As String = JsonConvert.SerializeObject(basePMSettngs, Formatting.Indented)

    ' Create a pmSettings object of this specific type of payment and inherit from the base class object
    Dim pmSettings As gtmaPayMethodAimACHSettings = JsonConvert.DeserializeObject(Of gtmaPayMethodAimACHSettings)(basePMSettingsJson)
2
Mark Sauer

@yboの答えを拡張-基本クラスのインスタンスは実際には派生クラスのインスタンスではないため、不可能です。基本クラスのメンバーについてのみ知っており、派生クラスのメンバーについては何も知りません。

派生クラスのインスタンスを基本クラスのインスタンスにキャストできるのは、派生クラスが既にそれらのメンバーを持っているため、実際には既に派生クラスが基本クラスのインスタンスであるためです。その反対は言えません。

2
Andy

拡張機能を使用できます:

public static void CopyOnlyEqualProperties<T>(this T objDest, object objSource) where T : class
    {
        foreach (PropertyInfo propInfo in typeof(T).GetProperties())
            if (objSource.GetType().GetProperties().Any(z => z.Name == propInfo.Name && z.GetType() == propInfo.GetType()))
                propInfo.SetValue(objDest, objSource.GetType().GetProperties().First(z => z.Name == propInfo.Name && z.GetType() == propInfo.GetType()).GetValue(objSource));
    }

コード内:

public class BaseClass
{
  public string test{ get; set;}
}
public Derived : BaseClass
{
//Some properies
}

public void CopyProps()
{
   BaseClass baseCl =new BaseClass();
   baseCl.test="Hello";
   Derived drv=new Derived();
   drv.CopyOnlyEqualProperties(baseCl);
   //Should return Hello to the console now in derived class.
   Console.WriteLine(drv.test);

}
1

(タイプキャストの代わりに)JsonConvertを使用したソリューション

今日、私は同じ問題に直面し、JsonConvertを使用してシンプルで問題の迅速な解決策を見つけました。

var base = new BaseClass();
var json = JsonConvert.SerializeObject(base);
DerivedClass derived = JsonConvert.DeserializeObject<DerivedClass>(json);
1
Jesse de gans

関係ないかもしれませんが、ベースが与えられた派生オブジェクトでコードを実行できました。それは私が望むよりも間違いなくハッキーですが、動作します:

public static T Cast<T>(object obj)
{
    return (T)obj;
}

...

//Invoke parent object's json function
MethodInfo castMethod = this.GetType().GetMethod("Cast").MakeGenericMethod(baseObj.GetType());
object castedObject = castMethod.Invoke(null, new object[] { baseObj });
MethodInfo jsonMethod = baseObj.GetType ().GetMethod ("ToJSON");
return (string)jsonMethod.Invoke (castedObject,null);
1
tstone2077

私はこれが古いことを知っていますが、私はかなり長い間これを首尾よく使用しました。

   private void PopulateDerivedFromBase<TB,TD>(TB baseclass,TD derivedclass)
    {
        //get our baseclass properties
        var bprops = baseclass.GetType().GetProperties();
        foreach (var bprop in bprops)
        {
            //get the corresponding property in the derived class
            var dprop = derivedclass.GetType().GetProperty(bprop.Name);
            //if the derived property exists and it's writable, set the value
            if (dprop != null && dprop.CanWrite)
                dprop.SetValue(derivedclass,bprop.GetValue(baseclass, null),null);
        }
    } 
1
Chris

ジェネリックを使用してこれを行うことができます。

public class BaseClass
{
    public int A { get; set; }
    public int B { get; set; }
    private T ConvertTo<T>() where T : BaseClass, new()
    {
         return new T
         {
             A = A,
             B = B
         }
    }

    public DerivedClass1 ConvertToDerivedClass1()
    {
         return ConvertTo<DerivedClass1>();
    }

    public DerivedClass2 ConvertToDerivedClass2()
    {
         return ConvertTo<DerivedClass2>();
    }
}

public class DerivedClass1 : BaseClass
{
    public int C { get; set; }
}

public class DerivedClass2 : BaseClass
{
    public int D { get; set; }
}

このアプローチを使用すると、3つの利点が得られます。

  1. コードを複製していません
  2. 反射を使用していません(遅い)
  3. コンバージョンはすべて1か所で
0
adeel41

すべての基本プロパティを派生アイテムに追加する最良の方法は、コンストラクターでリフレクションを使用することです。メソッドやインスタンスを作成せずにこのコードを試してください。

    public Derived(Base item) :base()
    {

        Type type = item.GetType();

        System.Reflection.PropertyInfo[] properties = type.GetProperties();
        foreach (var property in properties)
        {
            try
            {
                property.SetValue(this, property.GetValue(item, null), null);
            }
            catch (Exception) { }
        }

    }
0
Uraitz

私はそれが不可能であることに同意しません。次のようにできます:

public class Auto 
{ 
    public string Make {get; set;}
    public string Model {get; set;}
}

public class Sedan : Auto
{ 
    public int NumberOfDoors {get; set;}
}

public static T ConvertAuto<T>(Sedan sedan) where T : class
{
    object auto = sedan;
    return (T)loc;
}

使用法:

var sedan = new Sedan();
sedan.NumberOfDoors = 4;
var auto = ConvertAuto<Auto>(sedan);
0
Buzz Wilder

別の解決策は、次のような拡張メソッドを追加することです。

 public static void CopyProperties(this object destinationObject, object sourceObject, bool overwriteAll = true)
        {
            try
            {
                if (sourceObject != null)
                {
                    PropertyInfo[] sourceProps = sourceObject.GetType().GetProperties();
                    List<string> sourcePropNames = sourceProps.Select(p => p.Name).ToList();
                    foreach (PropertyInfo pi in destinationObject.GetType().GetProperties())
                    {
                        if (sourcePropNames.Contains(pi.Name))
                        {
                            PropertyInfo sourceProp = sourceProps.First(srcProp => srcProp.Name == pi.Name);
                            if (sourceProp.PropertyType == pi.PropertyType)
                                if (overwriteAll || pi.GetValue(destinationObject, null) == null)
                                {
                                    pi.SetValue(destinationObject, sourceProp.GetValue(sourceObject, null), null);
                                }
                        }
                    }
                }
            }
            catch (ApplicationException ex)
            {
                throw;
            }
        }

次に、基本クラスを受け入れる各派生クラスにコンストラクターがあります。

  public class DerivedClass: BaseClass
    { 
        public DerivedClass(BaseClass baseModel)
        {
            this.CopyProperties(baseModel);
        }
    }

また、すでに設定されている(nullでない)場合またはそうでない場合、宛先プロパティをオプションで上書きします。

0
d.popov

C#で明示的な型キャストを使用して、派生クラス参照に基本クラスオブジェクトを割り当てることは可能ですか?.

明示的な変換だけでなく、暗黙的な変換も可能です。

C#言語はそのような変換演算子を許可しませんが、純粋なC#を使用してそれらを記述することができ、それらは機能します。暗黙の変換演算子(Derived)を定義するクラスと、演算子を使用するクラス(Program)は、別々のアセンブリで定義する必要があります(たとえば、Derivedクラスはlibrary.dllは、Programクラスを含むprogram.exeによって参照されます)。

//In library.dll:
public class Base { }

public class Derived {
    [System.Runtime.CompilerServices.SpecialName]
    public static Derived op_Implicit(Base a) {
        return new Derived(a); //Write some Base -> Derived conversion code here
    }

    [System.Runtime.CompilerServices.SpecialName]
    public static Derived op_Explicit(Base a) {
        return new Derived(a); //Write some Base -> Derived conversion code here
    }
}

//In program.exe:
class Program {
    static void Main(string[] args) {
        Derived z = new Base(); //Visual Studio can show squiggles here, but it compiles just fine.
    }
}

Visual Studioのプロジェクト参照を使用してライブラリを参照すると、VSは暗黙的な変換を使用すると波線を表示しますが、コンパイルは正常に完了します。 library.dllを参照するだけであれば、波線はありません。

0
Ark-kun

以前の回答の一部(著者のおかげ)を組み合わせて、使用している2つのメソッドを持つ単純な静的クラスを作成しました。

はい、それは簡単です、すべてのシナリオをカバーするわけではありません、はい、それは拡張および改善することができます、いいえそれは完璧ではありません、はいそれはおそらくより効率的にすることができます、いいえそれはスライスされたパン以来最高ではありません完全に堅牢なnugetパッケージオブジェクトマッパーは、大量使用などに適しています。しかし、やだやだ-基本的なニーズには機能します:)

そしてもちろん、オブジェクトから派生または非派生オブジェクトに値をマッピングしようとします(もちろん同じ名前のパブリックプロパティのみ-残りは無視します)。

使用法:

SesameStreetCharacter puppet = new SesameStreetCharacter() { Name = "Elmo", Age = 5 };

// creates new object of type "RealPerson" and assigns any matching property 
// values from the puppet object 
// (this method requires that "RealPerson" have a parameterless constructor )
RealPerson person = ObjectMapper.MapToNewObject<RealPerson>(puppet);

// OR

// create the person object on our own 
// (so RealPerson can have any constructor type that it wants)
SesameStreetCharacter puppet = new SesameStreetCharacter() { Name = "Elmo", Age = 5 };
RealPerson person = new RealPerson("tall") {Name = "Steve"};

// maps and overwrites any matching property values from 
// the puppet object to the person object so now our person's age will get set to 5 and
// the name "Steve" will get overwritten with "Elmo" in this example
ObjectMapper.MapToExistingObject(puppet, person);

静的ユーティリティクラス:

public static class ObjectMapper
{
    // the target object is created on the fly and the target type 
    // must have a parameterless constructor (either compiler-generated or explicit) 
    public static Ttarget MapToNewObject<Ttarget>(object sourceobject) where Ttarget : new()
    {
        // create an instance of the target class
        Ttarget targetobject = (Ttarget)Activator.CreateInstance(typeof(Ttarget));

        // map the source properties to the target object
        MapToExistingObject(sourceobject, targetobject);

        return targetobject;
    }

    // the target object is created beforehand and passed in
    public static void MapToExistingObject(object sourceobject, object targetobject)
    {
        // get the list of properties available in source class
        var sourceproperties = sourceobject.GetType().GetProperties().ToList();

        // loop through source object properties
        sourceproperties.ForEach(sourceproperty => {

            var targetProp = targetobject.GetType().GetProperty(sourceproperty.Name);

            // check whether that property is present in target class and is writeable
            if (targetProp != null && targetProp.CanWrite)
            {
                // if present get the value and map it
                var value = sourceobject.GetType().GetProperty(sourceproperty.Name).GetValue(sourceobject, null);
                targetobject.GetType().GetProperty(sourceproperty.Name).SetValue(targetobject, value, null);
            }
        });
    }
}
0
AVH

どうですか:

public static T As<T>(this object obj)
    {
        return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(obj));
    }
0
Floare Emil