継承

# 継承。コンストラクターの呼び出しシーケンス

クラス Animal があるとします。 子クラス Dog を持つ

class Animal
{
    public Animal()
    {
        Console.WriteLine("In Animal's constructor");
    }
}

class Dog : Animal
{
    public Dog()
    {
        Console.WriteLine("In Dog's constructor");
    }
}

デフォルトでは、すべてのクラスは Object を暗黙的に継承します クラス。

これは上記のコードと同じです。

class Animal : Object
{
    public Animal()
    {
        Console.WriteLine("In Animal's constructor");
    }
}

Dog のインスタンスを作成する場合 クラスの場合、親クラスに別のコンストラクターへの明示的な呼び出しがない場合、基本クラスのデフォルト コンストラクター (パラメーターなし) が呼び出されます .私たちの場合、最初は Object's と呼ばれます コンストラクタ、次に Animal's そして最後に Dog's コンストラクター。

public class Program
{
    public static void Main()
    {
        Dog dog = new Dog();
    }
}

出力は

アニマルのコンストラクタで
犬のコンストラクタで

デモを見る

親のコンストラクターを明示的に呼び出します。

上記の例では、 Dog クラス コンストラクターは default を呼び出します Animal のコンストラクタ クラス。必要に応じて、どのコンストラクターを呼び出すかを指定できます。親クラスで定義されている任意のコンストラクターを呼び出すことができます。

この 2 つのクラスがあるとします。

class Animal
{
    protected string name;

    public Animal()
    {
        Console.WriteLine("Animal's default constructor");
    }    

    public Animal(string name)
    {
        this.name = name;
        Console.WriteLine("Animal's constructor with 1 parameter");
        Console.WriteLine(this.name);
    }
}

class Dog : Animal
{
    public Dog() : base()
    {
        Console.WriteLine("Dog's default constructor");
    }  

    public Dog(string name) : base(name)
    {
        Console.WriteLine("Dog's constructor with 1 parameter");
        Console.WriteLine(this.name);
    }
}

何が起こっているの?

各クラスに 2 つのコンストラクターがあります。

base とは 意味?

base 親クラスへの参照です。私たちの場合、 Dog のインスタンスを作成するとき このようなクラス

Dog dog = new Dog();

ランタイムは最初に Dog() を呼び出します 、これはパラメーターなしのコンストラクターです。しかし、その体はすぐには機能しません。コンストラクターの括弧の後に、次のような呼び出しがあります:base() 、つまり、デフォルトの Dog を呼び出すと、 コンストラクタで、親の default を呼び出します コンストラクタ。親のコンストラクターが実行された後、戻り、最後に Dog() を実行します。 コンストラクタ本体。

したがって、出力は次のようになります:

Animal のデフォルト コンストラクタ
犬のデフォルトのコンストラクター

デモを見る

Dog's を呼び出すとどうなるでしょうか パラメータを持つコンストラクタ?

Dog dog = new Dog("Rex");

プライベートではない親クラスのメンバーが子クラスに継承されることはわかっています。つまり、Dog name もあります 分野。
この場合、コンストラクターに引数を渡しました。次に、引数を親クラスのパラメータ付きコンストラクタに渡します name を初期化します。 フィールド。

出力は

Animal's constructor with 1 parameter
Rex
Dog's constructor with 1 parameter
Rex

まとめ:

すべてのオブジェクトの作成は基本クラスから始まります。継承では、階層にあるクラスが連鎖されます。すべてのクラスが Object から派生するため 、オブジェクトが作成されたときに呼び出される最初のコンストラクターは Object です クラス コンストラクター。次に、チェーン内の次のコンストラクターが呼び出され、すべてのコンストラクターが呼び出された後でのみ、オブジェクトが作成されます

基本キーワード

<オール>
  • base キーワードは、派生クラス内から基本クラスのメンバーにアクセスするために使用されます:
  • 別のメソッドによってオーバーライドされた基本クラスのメソッドを呼び出します。派生クラスのインスタンスを作成するときに、どの基本クラス コンストラクターを呼び出す必要があるかを指定します。
  • # 基本クラスからの継承

    コードの重複を避けるために、一般的なクラスで共通のメソッドと属性をベースとして定義します:

    public class Animal 
    {
        public string Name { get; set; }
        // Methods and attributes common to all animals
        public void Eat(Object dinner)
        {
            // ...
        }
        public void Stare()
        {
            // ...
        }
        public void Roll()
        {
            // ...
        }
    }
    
    

    Animal を表すクラスができました。 一般に、特定の動物の特徴を説明するクラスを定義できます:

    public class Cat : Animal
    {
        public Cat() 
        {
            Name = "Cat";
        }
        // Methods for scratching furniture and ignoring owner
        public void Scratch(Object furniture)
        {
            // ...
        }
    }
    
    

    Cat クラスは、その定義で明示的に記述されたメソッドだけでなく、一般的な Animal で定義されたすべてのメソッドにもアクセスできます。 基本クラス。どの動物も (猫であるかどうかにかかわらず) 食べたり、凝視したり、転がったりすることができます。ただし、動物が猫でない限り、動物はスクラッチできません。次に、他の動物を記述する他のクラスを定義できます。 (花畑を破壊するメソッドを持つ Gopher や、余分なメソッドがまったくないナマケモノなど)

    # クラスから継承してインターフェースを実装する

    public class Animal 
    {
        public string Name { get; set; }
    }
    
    public interface INoiseMaker
    {
        string MakeNoise();
    }
    
    //Note that in C#, the base class name must come before the interface names
    public class Cat : Animal, INoiseMaker
    {
        public Cat() 
        {
            Name = "Cat";
        }
    
        public string MakeNoise()
        {
            return "Nyan";
        }
    }
    
    

    # クラスから継承して複数のインターフェースを実装する

    public class LivingBeing
    {
        string Name { get; set; }
    }
    
    public interface IAnimal 
    {
        bool HasHair { get; set; }
    }
    
    public interface INoiseMaker
    {
        string MakeNoise();
    }
    
    //Note that in C#, the base class name must come before the interface names
    public class Cat : LivingBeing, IAnimal, INoiseMaker
    {
        public Cat() 
        {
            Name = "Cat";
            HasHair = true;
        }
    
        public bool HasHair { get; set; }
    
        public string Name { get; set; }
    
        public string MakeNoise()
        {
            return "Nyan";
        }
    }
    
    

    # 継承のテストとナビゲート

    interface BaseInterface {}
    class BaseClass : BaseInterface {}
    
    interface DerivedInterface {}
    class DerivedClass : BaseClass, DerivedInterface {}
    
    var baseInterfaceType = typeof(BaseInterface);
    var derivedInterfaceType = typeof(DerivedInterface);
    var baseType = typeof(BaseClass);
    var derivedType = typeof(DerivedClass);
    
    var baseInstance = new BaseClass();
    var derivedInstance = new DerivedClass();  
    
    Console.WriteLine(derivedInstance is DerivedClass); //True
    Console.WriteLine(derivedInstance is DerivedInterface); //True
    Console.WriteLine(derivedInstance is BaseClass); //True
    Console.WriteLine(derivedInstance is BaseInterface); //True
    Console.WriteLine(derivedInstance is object); //True
    
    Console.WriteLine(derivedType.BaseType.Name);  //BaseClass
    Console.WriteLine(baseType.BaseType.Name);  //Object
    Console.WriteLine(typeof(object).BaseType);  //null
    
    Console.WriteLine(baseType.IsInstanceOfType(derivedInstance));  //True
    Console.WriteLine(derivedType.IsInstanceOfType(baseInstance));  //False
    
    Console.WriteLine(
        string.Join(",", 
        derivedType.GetInterfaces().Select(t => t.Name).ToArray()));
    //BaseInterface,DerivedInterface
        
    Console.WriteLine(baseInterfaceType.IsAssignableFrom(derivedType)); //True
    Console.WriteLine(derivedInterfaceType.IsAssignableFrom(derivedType)); //True
    Console.WriteLine(derivedInterfaceType.IsAssignableFrom(baseType)); //False
    
    

    # 抽象基本クラスの拡張

    実装のコントラクトとして説明できるインターフェイスとは異なり、抽象クラスは拡張のコントラクトとして機能します。

    抽象クラスはインスタンス化できません。拡張する必要があり、結果のクラス (または派生クラス) をインスタンス化できます。

    抽象クラスは、一般的な実装を提供するために使用されます

    public abstract class Car
    {
        public void HonkHorn() {
            // Implementation of horn being honked
        }
    }
    
    public class Mustang : Car
    {
        // Simply by extending the abstract class Car, the Mustang can HonkHorn()
        // If Car were an interface, the HonkHorn method would need to be included
        // in every class that implemented it.
    }
    
    

    上記の例は、Car を拡張するクラスが実装で HonkHorn メソッドを自動的に受け取る方法を示しています。これは、新しい車を作成する開発者が、どのようにクラクションを鳴らすかを心配する必要がないことを意味します。

    # サブクラスのコンストラクタ

    基底クラスのサブクラスを作成する場合、: base を使用して基底クラスを構築できます。 サブクラス コンストラクターのパラメーターの後。

    class Instrument
    {
        string type;
        bool clean;
    
        public Instrument (string type, bool clean)
        {
            this.type = type;
            this.clean = clean;
        }
    }
    
    class Trumpet : Instrument
    {
        bool oiled;
    
        public Trumpet(string type, bool clean, bool oiled) : base(type, clean)
        {
            this.oiled = oiled;
        }
    }
    
    

    # 継承アンチパターン

    # 不適切な継承

    クラス Foo の 2 つのクラスがあるとします。 と Bar . Foo Do1 の 2 つの機能があります と Do2 . Bar Do1 を使用する必要があります Foo から 、しかし Do2 は必要ありません または Do2 と同等の機能が必要

    悪い方法 :Do2() にする Foo で virtual の場合は Bar でオーバーライドします または単に throw Exception BarDo2() の場合

    public class Bar : Foo
    {
        public override void Do2()
        {
            //Does something completely different that you would expect Foo to do
            //or simply throws new Exception 
        }
    }
    
    

    いいですね

    Do1() を取り出す Foo から それを新しいクラス Baz に入れます 次に両方の Foo を継承します と Bar Baz から Do2() を実装します 別々に

    public class Baz
    {
        public void Do1()
        {
            // magic
        }
    }
    
    public class Foo : Baz
    {
        public void Do2()
        {
            // foo way
        }
    }
    
    public class Bar : Baz
    {
        public void Do2()
        {
            // bar way or not have Do2 at all
        }
    }
    
    

    なぜ最初の例が悪く、2 番目の例が良いのか:開発者 nr2 が Foo で変更を行う必要がある場合 、おそらく彼は Bar の実装を壊すでしょう なぜなら Bar Foo と切り離すことはできません .後者の例で行う場合 Foo および Bar 共通性は Baz に移動しました そして、それらは互いに影響を与えません (すべきでないことのように)。

    # メソッドの継承

    メソッドを継承する方法はいくつかあります

    public abstract class Car
    {
        public void HonkHorn() {
            // Implementation of horn being honked
        }
    
        // virtual methods CAN be overridden in derived classes
        public virtual void ChangeGear() {
            // Implementation of gears being changed
        }
    
        // abstract methods MUST be overridden in derived classes
        public abstract void Accelerate();
    }
    
    public class Mustang : Car
    {
        // Before any code is added to the Mustang class, it already contains 
        // implementations of HonkHorn and ChangeGear.
    
        // In order to compile, it must be given an implementation of Accelerate,
        // this is done using the override keyword
        public override void Accelerate() {
            // Implementation of Mustang accelerating
        }
    
        // If the Mustang changes gears differently to the implementation in Car
        // this can be overridden using the same override keyword as above
        public override void ChangeGear() {
            // Implementation of Mustang changing gears
        }
    }
    
    

    # 再帰型指定の基底クラス

    再帰型指定子を使用したジェネリック基本クラスの 1 回限りの定義。各ノードには 1 つの親と複数の子があります。

    /// <summary>
    /// Generic base class for a tree structure
    /// </summary>
    /// <typeparam name="T">The node type of the tree</typeparam>
    public abstract class Tree<T> where T : Tree<T>
    {
        /// <summary>
        /// Constructor sets the parent node and adds this node to the parent's child nodes
        /// </summary>
        /// <param name="parent">The parent node or null if a root</param>
        protected Tree(T parent)
        {
            this.Parent=parent;
            this.Children=new List<T>();
            if(parent!=null)
            {
                parent.Children.Add(this as T);
            }
        }
        public T Parent { get; private set; }
        public List<T> Children { get; private set; }
        public bool IsRoot { get { return Parent==null; } }
        public bool IsLeaf { get { return Children.Count==0; } }
        /// <summary>
        /// Returns the number of hops to the root object
        /// </summary>
        public int Level { get { return IsRoot ? 0 : Parent.Level+1; } }
    }
    
    

    上記は、オブジェクトのツリー階層を定義する必要があるたびに再利用できます。ツリー内のノード オブジェクトは、

    を使用して基本クラスから継承する必要があります。
    public class MyNode : Tree<MyNode>
    {
        // stuff
    }
    
    

    各ノード クラスは、それが階層内のどこにあるか、親オブジェクトが何であるか、子オブジェクトが何であるかを認識しています。 Control のように、いくつかの組み込み型はツリー構造を使用します。 または XmlElement そして上記の Tree<T> any の基本クラスとして使用できます コードを入力してください。

    たとえば、すべての重量から合計重量が計算されるパーツの階層を作成するには 子供たちは、次のことを行います:

    public class Part : Tree<Part>
    {
        public static readonly Part Empty = new Part(null) { Weight=0 };
        public Part(Part parent) : base(parent) { }
        public Part Add(float weight)
        {
            return new Part(this) { Weight=weight };
        }
        public float Weight { get; set; }
    
        public float TotalWeight { get { return Weight+Children.Sum((part) => part.TotalWeight); } }
    }
    
    

    として使用する

    // [Q:2.5] -- [P:4.2] -- [R:0.4]
    //    \
    //      - [Z:0.8]
    var Q = Part.Empty.Add(2.5f);
    var P = Q.Add(4.2f);
    var R = P.Add(0.4f);
    var Z = Q.Add(0.9f);
    
    // 2.5+(4.2+0.4)+0.9 = 8.0
    float weight = Q.TotalWeight;
    
    

    もう 1 つの例は、相対座標フレームの定義です。この場合、座標フレームの実際の位置は すべて の位置に依存します 親座標フレーム。

    public class RelativeCoordinate : Tree<RelativeCoordinate>
    {
        public static readonly RelativeCoordinate Start = new RelativeCoordinate(null, PointF.Empty) { };
        public RelativeCoordinate(RelativeCoordinate parent, PointF local_position)
            : base(parent)
        {
            this.LocalPosition=local_position;
        }
        public PointF LocalPosition { get; set; }
        public PointF GlobalPosition
        {
            get
            {
                if(IsRoot) return LocalPosition;
                var parent_pos = Parent.GlobalPosition;
                return new PointF(parent_pos.X+LocalPosition.X, parent_pos.Y+LocalPosition.Y);
            }
        }
        public float TotalDistance
        {
            get
            {
                float dist = (float)Math.Sqrt(LocalPosition.X*LocalPosition.X+LocalPosition.Y*LocalPosition.Y);
                return IsRoot ? dist : Parent.TotalDistance+dist;
            }
        }
        public RelativeCoordinate Add(PointF local_position)
        {
            return new RelativeCoordinate(this, local_position);
        }
        public RelativeCoordinate Add(float x, float y)
        {
            return Add(new PointF(x, y));
        }
    }
    
    

    として使用する

    // Define the following coordinate system hierarchy
    //
    // o--> [A1] --+--> [B1] -----> [C1]
    //             |     
    //             +--> [B2] --+--> [C2]
    //                         |
    //                         +--> [C3]
    
    var A1 = RelativeCoordinate.Start;
    var B1 = A1.Add(100, 20);
    var B2 = A1.Add(160, 10);
    
    var C1 = B1.Add(120, -40);
    var C2 = B2.Add(80, -20);
    var C3 = B2.Add(60, -30);
    
    double dist1 = C1.TotalDistance;
    
    

    # 構文

    • クラス DerivedClass :BaseClass
    • クラス DerivedClass :BaseClass、IExampleInterface
    • クラス DerivedClass :BaseClass、IExampleInterface、IAnotherInterface

    # コメント

    クラスは 1 つのクラスからのみ直接継承できますが、(代わりに、または同時に) 1 つ以上のインターフェイスを実装できます。

    構造体はインターフェイスを実装できますが、どの型からも明示的に継承することはできません。 System.ValueType から暗黙的に継承します 、これは System.Object から直接継承します .

    静的クラスはインターフェースを実装できません。