C#8.0 基本理論第 6 章 -- クラス

C#8.0 本質主義 第 6 章 – クラス

クラスはオブジェクトのテンプレートとして理解できます。オブジェクト指向プログラミングの主な利点は、新しいプログラムを最初から作成するのではなく、既存のオブジェクトのセットを組み立てられることです。

6.1 クラスの宣言とインスタンス化

必須ではありませんが、通常は各クラスを独自のファイルに配置し、ファイルにクラス名を付ける必要があります。

メモリを割り当てるための新しい演算子はありますが、メモリを再利用するための対応する演算子はありません。メモリは特にガベージ コレクタによって再利用されます。

プログラマは、メモリを割り当てるのではなく、オブジェクトをインスタンス化するという new の役割を理解する必要があります。new 演算子は、ヒープとスタックの両方にオブ​​ジェクトを割り当てるためにサポートされています。これは、new がメモリ割り当てに関するものではなく、リサイクルが必要かどうかに関するものでもないことをさらに強調しています。

6.2 インスタンスフィールド

オブジェクト指向の用語では、クラスにデータを格納する変数はメンバー変数と呼ばれます。この用語は C# ではよく理解されていますが、より標準的で標準的な用語はフィールドです。

6.2.1 インスタンスフィールドの宣言
6.2.2 インスタンスフィールドへのアクセス

6.3インスタンスメソッド

6.4 このキーワードを使用する

これは暗黙的であり、オブジェクト自体のインスタンスを返します。

6.5 アクセス修飾子

カプセル化のもう 1 つの重要な役割は、オブジェクトのデータと動作の内部詳細を隠すことです。アクセス修飾子はカプセル化を提供します

6.6 プロパティ

6.6.1 属性の宣言

プロパティの重要な点は、プログラムの観点からフィールドと同様の API を提供することです。この定義では 3 つのコンテキスト キーワードが使用されており、get キーワードと set キーワードはそれぞれ属性の値部分と代入部分を表しており、代入メソッドでは value キーワードを使用して代入操作の右側の部分を参照できます。

    // LastName property
    public string LastName
    {
        get => _LastName;
        set => _LastName = value;
    }
    private string _LastName;
6.6.2 自動的に実装されるプロパティ

C# 3.0 には、簡略化されたバージョンのプロパティ構文があります。

    // Title property
    public string? Title { get; set; }
 
    // Manager property
    public Employee? Manager { get; set; }
 
    public string? Salary { get; set; } = "Not Enough";

C# 6.0 以降では、宣言時に初期化できるようになりました。

public string? Salary { get; set; } = "Not Enough";
6.6.3 属性とフィールドの設計仕様

クロージャーの原則に準拠するには、プロパティのバッキング フィールドをパブリックまたは保護として宣言しないでください。

自分で作成した完全なバージョンやフィールドではなく、自動的に実装されたプロパティを使用することを優先してください。

6.6.4 属性検証の提供

プロパティのバッキング フィールドには、プロパティの実装からのみアクセスすることをお勧めします。属性には検証コードが含まれている可能性があるためです。

まれではありますが、割り当てメソッドで値を割り当てることは確かに可能です。(value が左辺値であるという説明?)

nameof 演算子は識別子をパラメータとして受け取り、名前の文字列形式を返します。nameof の利点は、識別子が将来変更された場合、再構築ツールが実際のパラメータを自動的に変更できることです。

6.6.5 読み取り専用属性と書き込み専用属性

get の代わりに set を書くと書き込み専用を意味し、set を書かずに get のみを書くと読み取り専用を意味します。

プロパティに get アクセサーがない場合、その値はどこからも取得できません。get の前に private を追加すると、クラス内では取得できますが、他の場所では取得できません。

6.6.6 仮想フィールドとしてのプロパティ
6.6.7 値の取得および割り当てメソッドのアクセス修飾子
6.6.8 属性およびメソッド呼び出しは、ref および out パラメータ値として許可されません

ref パラメータと out パラメータは内部的にメモリ アドレスをターゲット メソッドに渡します。ただし、属性がサポートフィールドのない仮想フィールドである場合や、読み取り専用または書き込み専用である場合があるため、ストレージアドレスを渡すことはできません。同様のことがメソッド呼び出しにも当てはまります(個人的には右辺値によるものだと思いますが、ただし、C# にはそのようなステートメントはないようです。プロパティはフィールドのように動作しますが、メソッドに似ています)。

6.7コンストラクター

6.7.1 コンストラクターの宣言

new 演算子の実装の詳細: new は、メモリ マネージャーから「空白」メモリを取得し、指定されたコンストラクターを呼び出し、「空白」メモリへの参照を暗黙の this パラメーターとしてコンストラクターに渡します。コンストラクター チェーンの残りの部分が実行を開始し、コンストラクター間で参照を渡します。これらのコンストラクターには戻り値の型がありません。コンストラクター チェーンの実行が終了すると、new はメモリ参照を返します。この参照が指すメモリは完全に初期化された状態にあります。

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

クラスに明示的に定義されたコンストラクターがない場合、C# コンパイラーはコンパイル時に自動的にコンストラクターを追加します。デフォルトのコンストラクターと呼ばれますコンストラクターがクラスに明示的に追加されると、デフォルトのコンストラクターは追加されません。

6.7.3 オブジェクト初期化子

C# 3.0 では、新しいオブジェクト初期化子が追加されています。オブジェクトを作成するときに、次の中括弧のペアにメンバー初期化リストを追加できます。これは実際には糖衣構文の一種で、最終的に生成される CIL コードは、オブジェクトの作成後に個別にフィールドに値を割り当てるのと何ら変わりません。

    public static void Main()
    {
        Employee employee = new("Inigo", "Montoya") 
            { Title = "Computer Nerd", Salary = "Not enough" };
        // ...
    }

C# 3.0 では、コレクション初期化子も追加されています。

    public static void Main()
    {
        List<Employee> employees = new()
            {
                new("Inigo", "Montoya"),
                new("Kevin", "Bost")
            };
        // ...
    }

オブジェクトの破棄中に何が起こるかを定義するために、C# にはファイナライザーが用意されています。C++ デストラクターとは異なり、ファイナライザーは、オブジェクトへの参照がすべてなくなった直後には実行されません。対照的に、ファイナライザーは、オブジェクトが「到達不能」であると判断された後、不定の時間が経過してから実行されます。

6.7.4 コンストラクターのリファクタリング

コンストラクターの式本体メンバーの実装は、C# 7.0 以降でサポートされています。

public Employee(int id) => Id = id;
6.7.5 コンストラクター チェーン: これを使用して別のコンストラクターを呼び出す

オブジェクトを初期化するコードは複数の場所で繰り返されるため、複数の場所で維持する必要がありますが、あるコンストラクターを別のコンストラクターから呼び出すことは完全に可能です。これはコンストラクター チェーンと呼ばれ、コンストラクター初期化子を使用して実装されます。コンストラクター初期化子は、現在のコンストラクター実装を実行する前に他のどのコンストラクターを呼び出すかを決定します。

    public Employee(int id, string firstName, string lastName) : this(firstName, lastName)
    {
        Id = id;
    }

6.8 NULL 非許容の参照型属性とコンストラクター

6.8.1 読み取りおよび書き込み可能な参照型の非 null プロパティ
6.8.2 自動的に実装される読み取り専用参照プロパティ

6.9 Null 可能性機能

6.8~6.9追加予定…

6.10 デコンストラクター

コンストラクターを使用すると、複数のパラメーターを取得し、それらすべてを 1 つのオブジェクトにカプセル化できます。しかし、C# 7.0 より前には、その逆を行う明示的な言語構造はありませんでした。C# 7.0 でのタプル構文の導入以来、この操作は大幅に簡素化されました。

public class Employee
{
    // ...
    public void Deconstruct(out int id, out string firstName, out string lastName, out string? salary)
    {
       (id, firstName, lastName, salary) = (Id, FirstName, LastName, Salary);
    }
    // ...
}
 
public class Program
{
    public static void Main()
    {
        Employee employee;
        employee = new ("Inigo", "Montoya")
        {
            // Leveraging object initializer syntax
            Salary = "Too Little"
        };
        // ...
 
        employee.Deconstruct(out _, out string firstName,
            out string lastName, out string? salary);
    }
}

out パラメーターは呼び出しの前にインラインで宣言する必要があります。つまり、out を含める必要があります。

C# 7.0 以降では、オブジェクト インスタンスをタプルに直接割り当てることができるため、暗黙的に Deconstruct() メソッド (デストラクター関数) を呼び出すことができます。

タプル構文を使用した代入は、out パラメータに一致する走査でのみ許可されることに注意してください。タプル型の走査への代入は許可されません。

//可以
(_, string firstName, string lastName, string? salary) = employee;
//下面都不行
(int, string, string, string) tuple = employee;
(int id, string firstName, string lastName, string salary) tuple = employee

デコンストラクターを宣言するには、メソッド名は Deconstruct、シグネチャは return void、2 つ以上の out パラメーターを受け取る必要があります。(明示的に呼び出せばDeconstructする必要はなく、通常の関数とみなされますが、現時点ではタプル代入は使えません) (なお、outパラメータは無理のようで、現時点では必要ありません)

6.11 静的メンバー

6.11.1 静的フィールド
6.11.2 静的メソッド
6.11.3 静的コンストラクター

C# は、明示的な呼び出しを行わずにクラス (インスタンスではなく) を初期化するために使用される静的コンストラクターをサポートしています。「ランタイム」は、クラスに最初にアクセスしたときに、自動的に静的コンストラクターを呼び出します。

静的コンストラクターは明示的に呼び出すことができないため、パラメーターは許可されません静的コンストラクターの機能は、クラス内の静的データを特定の値に初期化することですが、たとえばメソッド呼び出しに関しては、宣言時に値を直接割り当てる方法はありません。

静的コンストラクターで宣言時に値を代入すると、どのような値が得られますか? CIL コードを観察すると、宣言中の代入が移動され、静的コンストラクターの最初のステートメントになっていることがわかりました。(つまり、静的メンバーを初期化してから静的コンストラクターを実行します) これはインスタンスフィールドと同じです。

静的コンストラクターで例外をスローしないでください。例外をスローしないと、その型はアプリケーションの存続期間が終わるまで使用できなくなります。

静的コンストラクターを使用するよりも、宣言時に静的初期化を実行することをお勧めします。

本の原文の言葉:

宣言中の静的初期化を優先する

静的コンストラクターは、静的フィールド、別の静的メンバー、インスタンス コンストラクターのいずれであっても、クラスのメンバーに最初にアクセスする前に実行されます。この実践をサポートするために、ランタイムはすべての静的メンバーとコンストラクターをチェックして、静的コンストラクターが最初に実行されることを確認します。

静的コンストラクターを使用しない場合、コンパイラーはすべての静的メンバーをデフォルト値に初期化し、静的コンストラクターのチェックの追加を回避します。その結果、静的割り当ての初期化は、静的フィールドにアクセスされる前に呼び出されますが、必ずしもすべての静的メソッドまたはインスタンス コンストラクターが呼び出される前に呼び出されるわけではありません。静的メンバーの初期化にコストがかかり、静的フィールドにアクセスする前に初期化が必要ない場合、これによりパフォーマンスが向上する可能性があります。このため、静的コンストラクターを使用せずに静的フィールドをインラインで初期化するか、宣言時に静的フィールドを初期化することを検討する必要があります。

6.11.4 静的プロパティ
6.11.5 静的クラス

一部のクラスにはインスタンス フィールドが含まれておらず、インスタンス化できるクラスを作成しても意味がないため、クラスを変更するには static キーワードを使用します。宣言時に static を使用することには 2 つの意味があります。1 つ目は、プログラマがインスタンスを作成するコードを作成できなくなること、そして 2 つ目は、インスタンス フィールドやメソッドがクラス内で宣言されることを防ぐことです。

C# コンパイラは、CIL コードを抽象およびシール済みとして自動的にマークし、拡張不可能であることを示します。

6.12拡張メソッド

C# 3.0 では、他のクラスのインスタンス メソッドの作成をシミュレートできる拡張メソッドの概念が導入されました。静的クラスの静的メソッドでは、最初のパラメータが拡張する型であり、その型の前に this キーワードを追加する必要があります。CIL コードを確認すると、拡張メソッドが通常の静的メソッドとして呼び出されていることがわかります。

拡張メソッドのシグネチャが拡張タイプの既存のシグネチャと一致する場合、拡張メソッドは通常の静的メソッドとして以外に呼び出されることはありません。

継承による型の特殊化は、拡張メソッドを使用するよりも優れています。拡張メソッドを使用すると、一致する署名が拡張型に追加されると、警告なしに既存の拡張メソッドが上書きされるため、明確なバージョン管理メカニズムの確立には役立ちません。もう 1 つの問題は、VS の IntelliSense は拡張メソッドをサポートしているため、コードだけを見た場合、メソッドが拡張メソッドであるかどうかを判断するのは簡単ではありません。したがって、拡張メソッドは注意して使用する必要があります。

6.13 カプセル化されたデータ

6.13.1定数

const フィールドにはコンパイル時に決定された値が含まれており、実行時に変更することはできません。

オブジェクト インスタンスごとに新しいフィールド インスタンスを生成する必要がないため、定数フィールドは自動的に静的フィールドになります。ただし、定数フィールドを明示的に静的として宣言すると、コンパイル エラーが発生します。定数フィールドは、リテラル値を持つ型としてのみ宣言できます。

パブリック定数は constant である必要があります。これを変更すると、それを使用するアセンブリに最新の変更が反映されない可能性があります。あるアセンブリが別のアセンブリの定数を適用すると、その定数は参照プログラムに直接コンパイルされます。したがって、参照先アセンブリの値が変更され、参照元アセンブリが再コンパイルされない場合、参照元アセンブリは新しい値ではなく元の値を引き続き使用します。

6.13.2読み取り専用

const とは異なり、readonly 修飾子はフィールドでのみ使用できます。これは、フィールド値がコンストラクターからのみ変更できるか、宣言時にイニシャライザーを通じて指定できることを示します。

読み取り専用フィールドはインスタンスまたは静的フィールドにすることができます。主な違いは、読み取り専用フィールドには実行時に値を割り当てることができることです。

もう 1 つの重要な特徴は、リテラル型に限定されないことです。

今後、仕様ではフィールド ビューにプロパティへの外部アクセスが含まれることが求められるため、C# 6.0 以降、readonly 修飾子はほぼ完全に使用されなくなりました。代わりに、常に読み取り専用の自動実装プロパティを選択してください。

6.14 入れ子になったクラス

クラス内に別のクラスを定義できます (ネストされたクラスと呼ばれます)。ネストされたクラスは、プライベート メンバーを含む、含まれるクラスの任意のメンバーにアクセスできますが、その逆はできません。

ネストされた public クラスの宣言は避けてください。これは不適切なコーディング スタイルであり、混乱を招き、読みにくくなる可能性があります。

6.15 配布クラス

配布クラスは、 C# 2.0 以降でサポートされています。配布クラスは、クラスの複数の部分です。コンパイラは、それらを完全なクラスにマージできます。目的は、クラスの定義を複数のファイルに分割することです。これは、クラスの生成または変更において非常に重要です。コードツールは特に便利です。

6.15.1 配布クラスの定義
// File: Program1.cs
partial class Program
{
}
// File: Program2.cs
partial class Program
{
}

コード ジェネレーターでの使用以外に、ネストされた各クラスを独自のファイルに配置することも一般的な用途です。

分散クラスは、コンパイルされたクラスや他のアセンブリ内のクラスを拡張することはできません。

6.15.2 配布方法

C# 3.0 では、配布クラスにのみ存在する配布メソッドの概念が導入されており、その主な機能はコード生成の利便性を提供することです。

コード生成ツールがデータベース内のテーブルに基づいてクラスに対応するファイルを生成できる場合、そのファイルを直接変更することはできず、再生成すると変更内容は失われます。代わりに、生成されたコードは 1 つのファイルに存在し、カスタム コードは別のファイルに存在します。

分散メソッドでは、実装せずにメソッドを宣言できます。

// File: Person.Designer.cs
public partial class Person
{
    #region Extensibility Method Definitions
    static partial void OnLastNameChanging(string value);
    static partial void OnFirstNameChanging(string value);
    #endregion Extensibility Method Definitions
 
    // ...
    public string LastName
    {
        get
        {
            return _LastName;
        }
        set
        {
            if (_LastName != value)
            {
                OnLastNameChanging(value);
                _LastName = value;
            }
        }
    }
    private string _LastName;
 
    // ...
    public string FirstName
    {
        get
        {
            return _FirstName;
        }
        set
        {
            if (_FirstName != value)
            {
                OnFirstNameChanging(value);
                _FirstName = value;
            }
        }
    }
    private string _FirstName;
 
    public partial string GetName();
}
 
// File: Person.cs
partial class Person
{
    static partial void OnLastNameChanging(string value)
    {
        value = value ?? 
            throw new ArgumentNullException(nameof(value));
 
        if (value.Trim().Length == 0)
        {
            throw new ArgumentException(
                $"{nameof(LastName)} cannot be empty.",
                nameof(value));
        }
    }
 
    // ...
 
    public partial string GetName() => $"{FirstName} {LastName}";
}

分散メソッドは void を返す必要があります。分散メソッドでは out パラメータは使用できません。値を返す必要がある場合は、ref パラメータを使用できます。分散メソッドを使用すると、生成されたコードから、必ずしも実装されていないメソッドを呼び出すことができます。

おすすめ

転載: blog.csdn.net/Story_1419/article/details/133235708