コンテンツにスキップ

C Sharp/クラスとメソッド

出典: フリー教科書『ウィキブックス(Wikibooks)』

クラスとオブジェクト指向の基本

[編集]

C#において、クラスはオブジェクト指向プログラミングの基本構成要素です。クラスはデータ(フィールドやプロパティ)と、そのデータを操作するメソッド(関数)を含みます。以下は基本的なクラスの例です。

using System;

// クラスの定義
public class MyClass {
  private string myField;

  public string MyProperty {
    get { return myField; }
    set { myField = value; }
  }

  public MyClass() {
    myField = "Default Value";
  }

  public void MyMethod() {
    Console.WriteLine("MyMethod is called!");
  }
}

class Program {
  static void Main(string[] args) {
    MyClass myObject = new MyClass();
    myObject.MyProperty = "New Value";
    Console.WriteLine(myObject.MyProperty);
    myObject.MyMethod();
  }
}

この例では、MyClassというクラスが定義され、プロパティMyPropertyやメソッドMyMethodを持っています。ProgramクラスのMainメソッドでインスタンス化され、プロパティやメソッドが利用されています。クラスを使用することで、データと振る舞いを一体化し、コードの構造化や保守性の向上が図れます。

フィールドとプロパティ

[編集]

フィールドとプロパティはクラス内でデータを保持する異なる手段です。それぞれに用途やアクセス方法が異なるため、適切に使い分けることで、クラスのカプセル化や安全性を高めることができます。

フィールド (Field)

[編集]

フィールドはクラス内で宣言される変数であり、通常はプライベートに設定され、クラスの内部処理でのみ使用されます。

public class MyClass {
  private int myField; // フィールドの宣言
  public void SetField(int value) {
    myField = value;
  }
}

プロパティ (Property)

[編集]

プロパティは、フィールドへのアクセスを安全に制御するためのメンバーであり、getsetアクセサーを使って外部からアクセスできます。

public class MyClass {
  private int myField;

  public int MyProperty {
    get { return myField; }
    set { myField = value; }
  }
}

フィールドとプロパティの主な違いは、プロパティがフィールドへのアクセスにロジックを追加できる点にあります。これにより、外部からのアクセスに対してバリデーションや制約を設定し、データの整合性を保つことが可能です。

staticメンバー

[編集]

staticなプロパティやメソッドは、インスタンスではなくクラスそのものに関連付けられ、インスタンス化せずに利用できます。

Staticプロパティ

[編集]
public class MyClass {
  public static string MyStaticProperty { get; set; } = "Initial Value";
}

この例のMyStaticPropertyは、MyClass.MyStaticPropertyとしてアクセス可能で、複数のインスタンスで共有される値を表すのに便利です。

Staticメソッド

[編集]
public class MyClass {
    public static void MyStaticMethod() {
        Console.WriteLine("This is a static method");
    }
}

MyStaticMethodMyClass.MyStaticMethod()として呼び出され、共通の処理を行う関数として利用されます。

アクセス修飾子

[編集]

C#では、privateprotectedpublicといったアクセス修飾子によってメンバーの可視性を制御します。

public class MyClass {
  private int privateField = 10;
  protected int protectedField = 20;
  public int publicField = 30;

  private void PrivateMethod() {
    Console.WriteLine("This is a private method");
  }

  protected void ProtectedMethod() {
    Console.WriteLine("This is a protected method");
  }

  public void PublicMethod() {
    Console.WriteLine("This is a public method");
  }
}

これらの修飾子は、オブジェクト指向プログラミングにおいて、クラスの内部状態を隠蔽(カプセル化)し、安全なデータ操作を促進します。

カプセル化(Encapsulation)は、オブジェクト指向プログラミングにおいて重要な概念の一つで、データとそのデータを操作するメソッドをひとまとめにして隠蔽することを指します。これにより、データの保護と、外部からの直接アクセスによる不正な変更を防ぐことが可能です。

C#では、アクセス修飾子を使うことで、クラスの外部からデータやメソッドへのアクセスを制限できます。たとえば、privateアクセス修飾子を使用することで、クラス内でのみデータやメソッドが利用できるようにし、外部からの干渉を防ぎます。また、必要に応じてpublicなプロパティやメソッドを通じて、外部から安全にデータにアクセスすることが可能になります。

次のコード例では、フィールドにprivateを使用して直接のアクセスを防ぎ、publicなプロパティを通じてアクセスできるようにするカプセル化の例を示しています。

public class Account {
  private decimal balance; // 直接アクセスから保護されたフィールド

  // balanceフィールドへのアクセスを提供するプロパティ
  public decimal Balance {
    get { return balance; }
    set {
      if (value >= 0) { // 入力値の検証(バリデーション)
        balance = value;
      }
    }
  }

  public void Deposit(decimal amount) {
    if (amount > 0) {
      balance += amount;
    }
  }
}

この例では、balanceフィールドはprivateとして宣言されているため、直接アクセスはできません。しかし、Balanceプロパティを介して安全にアクセス可能で、必要なバリデーションも行われます。カプセル化により、クラスの内部データを保護しながら、クラス外部に対して適切なインターフェースを提供できます。

クラスの継承

[編集]

C#において、継承(Inheritance)はクラス間でコードの再利用と拡張を可能にする機能です。あるクラスを基底クラス(親クラス)として定義し、別のクラスがそれを継承することで、基底クラスのメンバーを子クラスで利用できるようになります。

基底クラスから継承する際、: baseClassNameのように指定します。継承を使用することで、共通のプロパティやメソッドを一箇所にまとめ、コードの重複を避け、プログラム全体の管理を容易にします。

以下は、Animalクラスを基底クラスとして、Dogクラスがそれを継承する例です。

public class Animal {
  public void Eat() {
    Console.WriteLine("Eating...");
  }
}

public class Dog : Animal {
  public void Bark() {
    Console.WriteLine("Barking...");
  }
}

class Program {
  static void Main() {
    Dog dog = new Dog();
    dog.Eat(); // 基底クラスのメソッドを呼び出す
    dog.Bark(); // 子クラスのメソッドを呼び出す
  }
}

この例では、DogクラスはAnimalクラスを継承しており、Eatメソッドを利用可能です。また、Dogクラス固有のBarkメソッドも持っています。継承によってコードの再利用と拡張が実現され、オブジェクト指向の特性が強化されます。

メソッドのオーバーロード

[編集]

C#では、メソッドのオーバーロード(Overloading)によって、同じ名前のメソッドを異なるパラメータで定義することが可能です。これにより、メソッドを柔軟に利用でき、異なる状況に応じて同じ処理名を統一して使うことができます。

メソッドのオーバーロードは、引数の数や型が異なるメソッドを同じ名前で定義することで実現されます。以下は、メソッドのオーバーロードの例です。

public class Calculator {
  // 引数が2つの場合
  public int Add(int a, int b) {
    return a + b;
  }

  // 引数が3つの場合
  public int Add(int a, int b, int c) {
    return a + b + c;
  }

  // 引数がdouble型の場合
  public double Add(double a, double b) {
    return a + b;
  }
}

class Program {
  static void Main() {
    Calculator calculator = new Calculator();
    Console.WriteLine(calculator.Add(2, 3)); // 出力: 5
    Console.WriteLine(calculator.Add(2, 3, 4)); // 出力: 9
    Console.WriteLine(calculator.Add(2.5, 3.5)); // 出力: 6.0
  }
}

上記の例では、Addメソッドが異なる引数で3つ定義されています。プログラム実行時、C#は呼び出しに合ったオーバーロードされたメソッドを自動的に選択します。オーバーロードにより、同じ処理の名前を統一できるため、コードの可読性が向上します。

メソッドのオーバーライド

[編集]

メソッドのオーバーライド(Overriding)は、基底クラスのメソッドを派生クラスで再定義するための機能です。これにより、継承されたメソッドの動作を派生クラスで変更できます。オーバーライドを行うためには、基底クラスのメソッドにvirtual修飾子を付け、派生クラスでoverrideキーワードを使って再定義します。

以下は、メソッドのオーバーライドの例です。

public class Animal {
  public virtual void Speak() {
    Console.WriteLine("Animal sound");
  }
}

public class Dog : Animal {
  public override void Speak() {
    Console.WriteLine("Woof!");
  }
}

class Program {
  static void Main() {
    Animal myAnimal = new Animal();
    myAnimal.Speak(); // 出力: Animal sound

    Dog myDog = new Dog();
    myDog.Speak(); // 出力: Woof!
  }
}

上記の例では、基底クラスAnimalSpeakメソッドが定義され、virtual修飾子が付いています。Dogクラスでこのメソッドをoverrideし、派生クラス独自の動作を定義しています。オーバーライドにより、ポリモーフィズム(多態性)を活用でき、基底クラスのインターフェースを持ちながら派生クラスごとの動作を実現できます。

抽象クラスと抽象メソッド

[編集]

C#では、抽象クラス(Abstract Class)を利用して、クラスの基本構造を定義することができます。抽象クラスはインスタンス化できないクラスであり、他のクラスで継承されることを前提としています。抽象メソッド(Abstract Method)を含む場合、派生クラスでそのメソッドを必ず実装しなければなりません。

以下は、抽象クラスと抽象メソッドの例です。

public abstract class Shape {
  public abstract double GetArea(); // 抽象メソッド(必ず実装が必要)

  public void Display() {
    Console.WriteLine("Displaying shape area.");
  }
}

public class Circle : Shape {
  public double Radius { get; set; }

  public Circle(double radius) {
    Radius = radius;
  }

  public override double GetArea() {
    return Math.PI * Radius * Radius;
  }
}

class Program {
  static void Main() {
    Circle circle = new Circle(5);
    circle.Display();
    Console.WriteLine("Area: " + circle.GetArea());
  }
}

上記の例では、Shapeクラスが抽象クラスとして定義されており、抽象メソッドGetAreaを含んでいます。派生クラスCircleでは、この抽象メソッドをオーバーライドして具体的な実装を提供しています。抽象クラスを使用することで、共通の構造を定義しながら、派生クラスに実装の自由を与えることができます。

インターフェース

[編集]

インターフェース(Interface)は、メソッドやプロパティのシグネチャのみを定義し、実装は持たない抽象的な型です。インターフェースを使用することで、異なるクラス間で共通のインターフェースを提供し、コードの一貫性と柔軟性を高めることができます。

インターフェースの定義にはinterfaceキーワードを使用し、クラスに実装する場合は:でインターフェースを指定します。次の例では、IDrawableインターフェースを定義し、異なるクラスでそれを実装しています。

public interface IDrawable {
  void Draw();
}

public class Circle : IDrawable {
  public void Draw() {
    Console.WriteLine("Drawing a circle.");
  }
}

public class Square : IDrawable {
  public void Draw() {
    Console.WriteLine("Drawing a square.");
  }
}

class Program {
  static void Main() {
    IDrawable[] shapes = { new Circle(), new Square() };
    foreach (IDrawable shape in shapes) {
      shape.Draw();
    }
  }
}

この例では、IDrawableインターフェースにDrawメソッドが定義されており、CircleSquareクラスがそれを実装しています。インターフェースを利用することで、異なるクラスに共通のメソッドを持たせ、ポリモーフィズムを実現できます。

ジェネリクス

[編集]

C#では、ジェネリクス(Generics)を利用することで、型に依存しない汎用的なクラスやメソッドを作成できます。これにより、異なるデータ型に対応するコードを記述する必要がなくなり、再利用性が向上します。ジェネリクスは、コレクションやアルゴリズムを型に依存せずに設計するために非常に役立ちます。

ジェネリクスを使用するには、クラスやメソッドの宣言に型パラメータ(<T>など)を指定します。以下にジェネリクスを使用した例を示します。

public class GenericBox<T> {
  private T content;

  public void SetContent(T value) {
    content = value;
  }

  public T GetContent() {
    return content;
  }
}

class Program {
  static void Main() {
    GenericBox<int> intBox = new GenericBox<int>();
    intBox.SetContent(123);
    Console.WriteLine(intBox.GetContent()); // 出力: 123

    GenericBox<string> strBox = new GenericBox<string>();
    strBox.SetContent("Hello");
    Console.WriteLine(strBox.GetContent()); // 出力: Hello
  }
}

上記の例では、GenericBoxクラスが型パラメータTを持ち、int型やstring型など、任意の型に対応するインスタンスを作成できます。ジェネリクスを使うことで、コードの再利用性が向上し、型の安全性も確保されます。

ジェネリクスメソッド

[編集]

ジェネリクスメソッドは、クラスにジェネリクスを使用せずとも、メソッド単位でジェネリクスを適用できます。これにより、特定のメソッドのみ型パラメータを使用して柔軟な処理を実現できます。

public class Utilities {
  public static void Swap<T>(ref T a, ref T b) {
    T temp = a;
    a = b;
    b = temp;
  }
}

class Program {
  static void Main() {
    int x = 10, y = 20;
    Utilities.Swap(ref x, ref y);
    Console.WriteLine($"x: {x}, y: {y}"); // 出力: x: 20, y: 10

    string str1 = "Hello", str2 = "World";
    Utilities.Swap(ref str1, ref str2);
    Console.WriteLine($"str1: {str1}, str2: {str2}"); // 出力: str1: World, str2: Hello
  }
}

上記の例では、Swapメソッドが型パラメータTを持っており、int型やstring型など異なる型に対応した入れ替え処理を行っています。

制約付きジェネリクス

[編集]

ジェネリクスに制約(Constraints)を追加することで、型パラメータとして利用できる型を制限できます。これにより、特定の条件を満たす型のみを使用できるようになります。制約にはwhereキーワードを使います。

以下に制約付きジェネリクスの例を示します。

public class Calculator<T> where T : struct {
  public T Add(T a, T b) {
    dynamic x = a;
    dynamic y = b;
    return x + y;
  }
}

class Program {
  static void Main() {
    Calculator<int> intCalculator = new Calculator<int>();
    Console.WriteLine(intCalculator.Add(2, 3)); // 出力: 5

    Calculator<double> doubleCalculator = new Calculator<double>();
    Console.WriteLine(doubleCalculator.Add(2.5, 3.5)); // 出力: 6.0
  }
}

この例では、Calculatorクラスの型パラメータTwhere T : structという制約があり、値型(構造体)であるintdoubleなどが使用可能です。制約付きジェネリクスを使用すると、特定の型のメンバーを利用したり、パフォーマンスを向上させるための最適化を行いやすくなります。

Nullable型

[編集]

C#では、Nullable型(またはT?として表記)を使用して、通常の値型にnull値を許容させることができます。これにより、整数や浮動小数点数など、値型にnullの状態を表現する機能が追加され、データの欠損や未初期化状態を表現できます。

int? nullableInt = null;
nullableInt = 10;

if (nullableInt.HasValue) {
  Console.WriteLine(nullableInt.Value); // 出力: 10
} else {
  Console.WriteLine("No value");
}

Nullable型は、特にデータベースやデータの欠損が発生する可能性があるシステムで役立ちます。

タプル

[編集]

タプル(Tuple)は、複数の値を一度にまとめて返したい場合や、簡単なデータ構造として使用されます。C#では、タプル型を利用して、メソッドから複数の戻り値を返すことができます。

public (int, string) GetPerson() {
  int age = 25;
  string name = "John";
  return (age, name);
}

class Program {
  static void Main() {
    var (age, name) = new Program().GetPerson();
    Console.WriteLine($"Age: {age}, Name: {name}"); // 出力: Age: 25, Name: John
  }
}

この例では、GetPersonメソッドが(int, string)型のタプルを返し、複数の戻り値として使用されています。タプルを使うと、一時的なデータ構造として複数の値を管理できます。

名前付きタプル

[編集]

タプルのフィールドには名前を付けることも可能で、コードの可読性が向上します。

public (int Age, string Name) GetPerson() {
  int age = 25;
  string name = "John";
  return (age, name);
}

class Program {
  static void Main() {
    var person = new Program().GetPerson();
    Console.WriteLine($"Age: {person.Age}, Name: {person.Name}"); // 出力: Age: 25, Name: John
  }
}

このように、タプルにフィールド名を付けることで、使用時に名前でアクセスでき、読みやすいコードが書けます。