C#をPythonと比較して学ぶ(第三回:部品化と再利用)
2024/07/18 C# Python 比較シリーズ
はじめに:
「C#をPythonと比較して学ぶ」シリーズの第三回では、プログラミングにおける部品化と再利用の重要性に焦点を当て、これまでの学びをさらに発展させていきます。第一回記事ではデータに、第二回記事では処理に焦点を当て、基本的なプログラミング概念やC#とPythonの比較を通じて言語仕様を理解しました。
第一回記事では、C#とPythonという異なるプログラミング言語の特徴や使用領域を比較し、プログラミングの基本的な要素であるデータ、処理、部品化と再利用の重要性に言及しました。第二回記事では、順次、選択、反復の制御構文やエラーに焦点を当て、C#とPythonの文法や機能の比較を行いました。これにより、両言語の利点や違いを理解し、効果的なコーディングのポイントを学びました。
第三回記事では、「部品化と再利用」に焦点を当て、プログラムの品質向上とメンテナンス性の向上に寄与する方法について学びます。具体的には、既存の部品やライブラリの活用方法、ステートレスメソッドとステートフルメソッドの違い、メソッドのオーバーロード、クラスとメソッドの設計、オブジェクト指向プログラミングの基本概念であるカプセル化、コンストラクタとプロパティ、継承、ポリモーフィズムについて深く掘り下げます。
これらのトピックを通じて、プログラムをより効果的に構築し、保守性を高めるための実践的なスキルを身につけます。部品化と再利用は、大規模で複雑なプロジェクトにおいて特に重要であり、本記事を通じてその重要性と実践的な手法を習得していくことで、より洗練されたプログラミングスキルを身につけることができるでしょう。
ライブラリと部品化:
ライブラリは、プログラミングにおいて非常に重要な役割を果たしています。これは、既存のコードや関数、クラスを部品化して提供しているものであり、開発者が特定の機能を容易に利用できるようになっています。以下に、ライブラリに関するいくつかの要素に焦点を当てて説明します。
ライブラリとパッケージとモジュール:
-
ライブラリは、関数、クラス、モジュール、パッケージなどのコードがまとめられたものです。
-
パッケージは、複数のモジュールをまとめたもので、通常はフォルダに対応します。
-
モジュールは、Pythonの場合、関数やクラスをまとめたPythonファイルで、拡張子が「.py」です。
ライブラリの利用:
例えば、C#で乱数を生成する際、Random()クラスを使用します。これはusing Systemと記載することで利用できるライブラリの一部です。
ライブラリ内のメソッドを呼び出すには、インスタンスを生成し、そのインスタンスのメソッドを呼び出す形になります。
コード例:
以下は、C#での乱数生成の例です。
using System;
class Program
{
static void Main()
{
var dice = new Random();
int randomNumber = dice.Next(1, 7);
Console.WriteLine($"Random Number: {randomNumber}");
}
}
using SystemによりRandomクラスが使用可能になり、dice.Next(1, 7)で乱数が生成されています。
オーバーロード:
オーバーロードは、同じ名前のメソッドや関数を複数定義し、引数の数や型によって異なる動作をするようにするプログラミングの機能です。これにより、同じ役割を果たす機能を、柔軟に利用できるようになります。
オーバーロードとは:
オーバーロードは同じ名前のメソッドや関数を異なるパラメータで複数定義することを指します。 パラメータの型や数が異なる場合、同じ名前のメソッドでも区別され、異なる挙動を示すことができます。
PythonとC#の比較:
Python:
Pythonではオーバーロードが直接サポートされていません。代わりに、可変長引数やデフォルト引数を使用して同様の機能を実現することがあります。
可変長引数を使用すると、引数の数が可変であるメソッドを実装できます。これにより、同じ名前のメソッドで異なる引数の数に対応できます。
def example_method(*args):
if len(args) == 1:
print(f"Method with 1 argument: {args[0]}")
elif len(args) == 2:
print(f"Method with 2 arguments: {args[0]}, {args[1]}")
# 使用例
example_method(10) #Method with 1 argument: 10
example_method(20, 30) #Method with 2 argument: 20, 30
C#:
C#ではオーバーロードが直接サポートされており、同じ名前のメソッドを異なる引数で複数定義できます。
引数の型や数が異なる場合、それぞれのメソッドが呼び出され、異なる処理を実行できます。
class ExampleClass
{
public void ExampleMethod(int x)
{
Console.WriteLine($"Method with 1 argument: {x}");
}
public void ExampleMethod(int x, int y)
{
Console.WriteLine($"Method with 2 arguments: {x}, {y}");
}
}
// 使用例
var exampleObj = new ExampleClass();
exampleObj.ExampleMethod(10);
exampleObj.ExampleMethod(20, 30);
これにより、同じ名前のメソッドがコード内で使いやすく、柔軟性が向上します。オーバーロードは開発者にとって便利な機能であり、コードの可読性や保守性を向上させる一因となります。
クラスやメソッドを扱う際に、上記のコードの記述の読み方などは学んでいきます。オーバーロードができるということだけ知っておいてください。
では次の章からは、クラスやメソッド、関数をどのように自作していけるか見ていきましょう。
可読性、変更容易性、再利用性:
プログラミングにおいて、よいコードとは複数の要素が調和して達成されるものです。その中でも、可読性、変更容易性、再利用性は特に重要です。
可読性:
可読性が高いコードは他の開発者が理解しやすく、メンテナンスがしやすいです。
適切な変数名やコメントを使用し、コードブロックを整理することで、コードが意味することが一目で分かります。
変更容易性:
変更容易性が高いコードは新しい機能の追加や既存のバグ修正がスムーズに行えます。
コードがモジュール化されていると、変更が局所的になり、影響範囲が限定されます。
再利用性:
再利用性が高いコードは同じ機能が他の場所でも利用できます。
部品化されたコードは他のプロジェクトやモジュールで再利用され、開発効率を向上させます。
これらの要素が組み合わさったコードは、保守性が高まり、柔軟性が増します。これにより、プロジェクトが進化するにつれて発生する変更や拡張に対応できるようになります。
データの部品化と処理の部品化:
クラス:
クラスはデータの部品化を担当します。関連するデータやそれに関連する操作をまとめて扱うことができます。
クラスはオブジェクト指向プログラミングにおいて中核的な概念であり、データとそれに対する操作をカプセル化します。
クラスを使用することで、関連する変数やメソッドが一つの単位として扱え、コードの構造が整理されます。
メソッド:
メソッドは処理の部品化を担当します。特定の機能や操作をメソッド単位でまとめ、再利用性を高めます。
関数や手続きとも呼ばれるメソッドは、一連の処理をまとめて呼び出すことができるため、コードの重複を避けます。
メソッドの使い方によっては、可読性も向上します。
これにより、データと処理を適切に部品化することで、コードの理解が容易になり、変更がしやすい構造が構築されます。クラスとメソッドの組み合わせによって、プログラムの設計がより効果的に行えます。
以下で、具体的にこの部品化とはどのようなことなのかを学んでいきましょう。
クラス
クラスとフィールドとは:
クラスはオブジェクト指向プログラミングにおいて、データとそのデータに対する操作を組み合わせたものです。クラスは設計図のようなものであり、実際に利用する際にはこの設計図をもとにオブジェクト(インスタンス)を生成します。
フィールドはクラス内で宣言された変数であり、そのクラスが持つデータを表します。これにより、関連するデータを一つの単位でまとめて管理できます。
クラスでのフィールドの定義方法(C#とPythonの比較):
C#:
class Person
{
// フィールドの定義
public string? name;
public int age;
}
Python
class Person:
# フィールドの定義
def __init__(self):
self.name = ""
self.age = 0
このPersonクラスを作ることによって、名前と年齢のデータを一元管理しやすくなります。たとえば、var User = new Person()
とインスタンスを作成し、User.name = "Taro"
と User.age = 22
と設定することができます。すると、なにかの引数に渡す場合では、User一つを渡せばよく、.nameや.ageで簡単にいつでも名前や年齢が取り出せるため、扱いやすいことが実感できるのではないでしょうか。
補足:string? nameの意味:
C#におけるstring? nameの記法は、Nullable Reference Types(null 許容参照型)の機能を示しています。この機能は、通常の参照型がnullを許容するかどうかを明示的に指定するものです。string?は、string型に対してnullが許容されることを示しています。
この機能を使うことで、nullになりうる変数を宣言するときにコンパイラが警告を発生させ、null参照エラーを事前に防ぐことができます。
クラスのフィールドによって、データの部品化として可読性や変更容易性、再利用性がどのように良くなったか:
可読性:
フィールドによって関連するデータがまとめられ、クラス内で一元管理されるため、可読性が向上します。例えば、PersonクラスのNameやAgeは、このクラスがどのようなデータを持つかを明示的に示しています。
変更容易性:
フィールドにより、データの構造がクラス内に隠蔽され、実際のデータ構造がどのようになっているか知らずに利用できます。そのため、変更時も局所的になります。クラス内のフィールドが変更された場合、そのクラスを利用する他の部分には影響が及びません。これにより変更が容易になります。
再利用性:
クラスとフィールドによって、データとそのデータに対する操作がひとまとまりの部品として構築されます。これにより、同じデータ構造が必要な場合にはそのクラスを再利用することができ、コードの再利用性が向上します。
メソッド
クラス内にメソッドを定義する方法(C#とPythonの比較):
足し算をする簡単なメソッドAdd(a,b)が存在した場合、a,bを引数と言い、c = Add(a,b)でのcには返り値a+bが渡されます。このようなメソッドの作り方を見ていきましょう。
C#
class Calculator
{
// メソッドの定義
public int Add(int a, int b)
{
return a + b; //返り値
}
}
Python
class Calculator:
# メソッドの定義
def add(self, a, b):
return a + b #返り値
C#におけるメソッドの記法:
C#において、メソッドの前についている型名は、返り値の型を示しています。このようにメソッド名の前についているものをメソッドの修飾子と呼びます。例えば、public int Addは、Addメソッドがpublicアクセス修飾子を持ち、int型を返すことを示しています。
引数に指定されている型名は、そのメソッドが受け取る引数の型を示しています。例えば、int a, int bは、aとbが整数型であることを示しています。
Pythonにおけるメソッドの記法:
Pythonでは、メソッドを定義する際に、def メソッド名(self, 引数):
と書きます。クラスの外側の場合、selfはいりませんが、クラス内部で定義するメソッドの最初の引数には、必ずselfを指定してください。このselfはクラスのフィールド変数を格納する機能などが搭載されています。(使い方は後述)
返り値がない場合のメソッドの記載方法:
C#
class Printer
{
// 返り値がないメソッドの定義
public void PrintMessage(string message)
{
Console.WriteLine(message);
}
}
Python
class Printer:
# 返り値がないメソッドの定義
def print_message(self, message):
print(message)
返り値がない場合、C#ではvoidを返り値の型名にいれます。Pythonではreturn文を書かなければ、返り値が発生しません。
public、privateとは:
publicおよびprivateはアクセス修飾子であり、メソッドやフィールドがどれだけの範囲でアクセス可能かを指定します。この記法はPythonにはありません。
public:
publicメソッドやフィールドは、どこからでもアクセス可能です。他のクラスからも利用できます。
private:
privateメソッドやフィールドは、同じクラス内でのみアクセス可能です。外部からは見えません。この修飾子は情報隠蔽(封じ込め)を提供し、クラス内の実装の詳細を外部から隠す役割を果たします。
using System;
class MyClass
{
// フィールドの定義 (private)
private string myPrivateField = "Private Field";
// メソッドの定義 (private)
private void MyPrivateMethod()
{
Console.WriteLine("Private Method");
}
// メソッドの定義 (public)
public void MyPublicMethod()
{
Console.WriteLine("Public Method");
// privateなフィールドとメソッドにアクセス
Console.WriteLine($"Accessing private field: {myPrivateField}");
MyPrivateMethod();
}
}
class Program
{
static void Main()
{
// クラスのインスタンスを生成
MyClass myObject = new MyClass();
// publicメソッド経由でprivateメソッドやフィールドにアクセス
myObject.MyPublicMethod();
// privateフィールドへの直接アクセスはエラーとなる
// Console.WriteLine(myObject.myPrivateField); // エラー
// myObject.MyPrivateMethod(); // エラー
}
}
private
フィールドmyPrivateField
およびprivate
メソッドMyPrivateMethod
がクラス内で定義されています。public
メソッドMyPublicMethod
からは、private
フィールドとprivate
メソッドにアクセスできます。Main
メソッドからは、クラスのインスタンスを生成し、そのインスタンスを通じてMyPublicMethod
を呼び出すことで、private
メソッドとprivate
フィールドにアクセスできます。アクセス修飾子には他にprotected や internalもあります。
Pythonではこのようなpublic, privateの記法は存在しませんが、慣習的に__func__のような__で挟まれた関数などはprivateなものであると考えられます。
ラムダ式
ラムダ式について:
ラムダ式は、無名の関数を簡潔に表現する方法です。通常、小さな関数や一時的な処理を行う際に使用されます。ラムダ式は主に関数型プログラミング言語やサポートする言語で利用され、簡潔な構文で関数を定義することができます。
基本的な構文は以下のようになります:
Python:
lambda arguments: expression
C#:
(parameters) => expression
この構文には次の要素が含まれます:
-
引数 (arguments/parameters):
- 関数に渡される引数やパラメータを表します。
-
=>
(アロー演算子):- ラムダ式の左辺が引数、右辺が式となります。
-
式 (expression):
- 関数が評価された際に返される値を表します。
例:
-
Python:
add = lambda a, b: a + b
-
C#:
var add = (a, b) => a + b;
上記の例では、a
と b
を引数とし、その合計を返すラムダ式が定義されています。ラムダ式は簡潔でありながらも強力な機能を提供し、特に関数を第一級のオブジェクトとして扱う関数型プログラミングの特性を強調しています。
Pythonでの同義な通常の関数とラムダ式の比較:
-
通常の関数:
def add_func(a, b): return a + b
-
ラムダ式:
add_lambda = lambda a, b: a + b
-
説明:
- 通常の関数とラムダ式は同等の機能を提供します。ラムダ式はシンプルな関数を1行で書くための便利な方法です。
C#での通常の関数の書き方:
int Add(int a, int b)
{
return a + b;
}
C#で同義な関数を匿名関数 delegate
で書く書き方:
Func<int, int, int> addDelegate = delegate (int a, int b)
{
return a + b;
};
さらに同義な関数をラムダ式として書く書き方:
Func<int, int, int> addLambda = (a, b) => a + b;
- 説明:
- 匿名関数
delegate
とラムダ式は同等の機能を提供しますが、ラムダ式の方がシンプルでコンパクトな書き方ができます。
- 匿名関数
名前空間について:
名前空間は、コード内でクラスやメソッドが定義される際の仮想的なコンテナであり、名前の衝突を防ぐために使用されます。名前空間は、関連するクラスや要素を論理的にグループ化し、整理されたコード構造を提供します。
-
名前空間やクラス名の使用意義:
- 名前空間を使うことで、異なる場所で同じ名前のクラスが定義されても、それらが区別されます。名前空間がない場合、同じ名前のクラスが存在すると名前の衝突(名前の被り)が発生します。
-
名前空間やクラス名の使われ方:
Namespace.Class.Method()
のように、名前空間を含んだ形でクラスやメソッドが呼び出されます。これにより、同じ名前のクラスが別の名前空間にあっても正確に指定できます。
-
名前空間やクラス名の省略と探索:
- 名前空間やクラス名が省略された場合、通常は自分の名前空間やクラス名から探索が始まります。つまり、自分のクラス内などでは、クラス名を省略できるということです。C#では、
using
ステートメントを使って他の名前空間を指定し、名前空間を省略する事もできます。(例:using System
)
- 名前空間やクラス名が省略された場合、通常は自分の名前空間やクラス名から探索が始まります。つまり、自分のクラス内などでは、クラス名を省略できるということです。C#では、
-
変数のスコープ:
- 名前空間内やクラス内で定義された変数は、そのスコープ内(≒{}内)でのみ有効です。変数のスコープは、その変数がどこからアクセス可能かを示します。スコープ外からのアクセスはエラーとなります。
-
using
ステートメント:using
ステートメントを使用すると、特定の名前空間を一時的にインポートし、その名前空間に含まれるクラスやメソッドを簡潔に使用できます。これにより、冗長な修飾を避けてコードをスッキリとさせることができます。
-
using
ステートメントの注意点:using
ステートメントを使うときには、クラス名が衝突する可能性がある場合は、名前空間やエイリアスを使用して区別する必要があります。
コード例:
// 1. 名前空間やクラス名の使用意義
namespace MyNamespace
{
class MyClass
{
public void MyMethod()
{
Console.WriteLine("MyMethod called");
}
}
}
namespace AnotherNamespace
{
class MyClass
{
public void MyMethod()
{
Console.WriteLine("AnotherNamespace.MyMethod called");
}
}
// 2. 名前空間やクラス名の使われ方
MyNamespace.MyClass myObject = new MyNamespace.MyClass();
myObject.MyMethod(); // "MyMethod called" を出力
AnotherNamespace.MyClass anotherObject = new AnotherNamespace.MyClass();
anotherObject.MyMethod(); // "AnotherNamespace.MyMethod called" を出力
// 3. 名前空間やクラス名が省略された場合、自分の名前空間やクラス名から探す
MyClass anotherInstance = new MyClass();
anotherInstance.MyMethod(); // "AnotherNamespace.MyMethod called" を出力
}
// 4. 変数のスコープ
namespace ScopeExample
{
class ScopeClass
{
public void ExampleMethod()
{
int localVar = 10; // メソッド内でのみ有効な変数
Console.WriteLine(localVar); // 10 を出力
}
}
}
// localVar はこのスコープ外では存在しない
// 5. using ステートメント
using System;
class UsingExample
{
static void Main()
{
Console.WriteLine("Using Statement Example"); //Consoleがあるのは、Systemという名前空間であるが、usingを使っているため、省略する事ができる。
}
}
このコード例では、名前空間、クラス、変数のスコープ、using
ステートメントの各要素が示されています。
オブジェクト指向について:
オブジェクト指向とは:
オブジェクト指向(Object-Oriented)は、プログラミングのパラダイムの一つであり、現実世界の概念や事物をモデル化し、それらをソフトウェア内でオブジェクトとして扱います。
たとえば、ある自動車をプログラムで表現する場合、その自動車はブランド、モデル、速度、燃料などの属性(データ)を持ち、走行や停止といった振る舞い(メソッド)を持っています。オブジェクト指向では、このような概念をプログラム内の「オブジェクト」として表現します。
オブジェクト指向を使うときに有用な方法論:
オブジェクト指向は、以下の三つの方法論で構築されます。
-
カプセル化(Encapsulation):
- カプセル化は、オブジェクト内のデータや振る舞いを一つのまとまり(カプセル)に封じ込め、外部からの直接アクセスを制限する概念です。これにより、オブジェクトの内部実装を隠蔽し、外部とのインターフェースを簡潔に保ちます。
-
継承(Inheritance):
- 継承は、既存のクラス(親クラス)を基にして新しいクラス(子クラス)を作成する方法です。子クラスは親クラスの特性や振る舞いを受け継ぎ、必要に応じて拡張や変更ができます。これにより、コードの再利用性が向上します。
-
ポリモーフィズム(Polymorphism):
- ポリモーフィズムは、同じ名前のメソッドが複数のクラスで異なる振る舞いをすることを指します。同じメソッド名を使用して、異なるクラスやオブジェクトに対して適切な処理を行うことができます。
カプセル化とは:
カプセル化は、データ(属性)と振る舞い(メソッド)を一つのまとまりにして、外部からのアクセスを制限する概念です。例えば、自動車オブジェクトの速度や燃料量は外部から直接変更されるべきではありません。運転している際に、変に中のパラメーターを触って、使用する燃料の量や速度の変化の仕方が変わってしまうと非常に危ないです。そのため、代わりに、これらのデータにアクセスするための公開されたメソッド(getterやsetter)を提供します。自動車オブジェクトであれば、アクセル・ブレーキによってのみ、速度を変化させられるようにするということです。これにより、オブジェクトの内部実装を変更せずに、外部インターフェースを変更できます。
継承とは:
継承は、既存のクラス(親クラスまたは基底クラス)の特性や振る舞いを新しいクラス(子クラスまたは派生クラス)に引き継ぐ方法です。子クラスは親クラスのメンバーを再利用できるだけでなく、必要に応じて追加や変更が可能です。これにより、コードの再利用性が向上し、階層的なクラス構造を構築できます。自動車クラスであれば、軽自動車クラスも、トラッククラスも自動車と同じように速度や燃料というフィールドを持つでしょう。さらに、トラッククラスであれば、積荷に関する情報を付与したいと思うかもしれません。もちろん、トラッククラスの中には、2tトラックや3tトラッククラスを作れます。この場合、自動車クラスから見ると、2tトラッククラスは孫クラスとなります。
ポリモーフィズムとは:
ポリモーフィズムは、同じ名前のメソッドが異なるクラスで異なる振る舞いをすることを指します。例えば、自動車クラスにDrive()
というメソッドがあるとします。しかし、車の具体的な運転の仕方は、AT車やMT車、トラックやバスで異なります。つまりこのメソッドは同じ名前で呼び出せますが、実際には各クラスによって異なる処理が実行されるという仕組みです。
これらのオブジェクト指向の方法論は、大規模で柔軟で保守しやすいソフトウェアの開発に寄与します。オブジェクト指向の利点は、現実世界の複雑さをモデル化し、それに基づいてシステムを設計できる点にあります。
カプセル化について:
-
Python:
-
コンストラクタ、プロパティとは:
- コンストラクタ(Constructor)は、クラスのインスタンスが作成される際に呼び出される特別なメソッドで、オブジェクトの初期化を担当します。
- プロパティ(Property)は、クラスの外部からアクセスできるように公開されたメンバーで、内部的にはゲッター(getter)とセッター(setter)メソッドによってアクセス制御が行われます。
-
Pythonでのカプセル化の方法:
-
Pythonでは、通常のメソッドや変数の前にアンダースコア
_
を1つ付けることで、外部からの直接アクセスをしないとする慣習があります。これは厳密な制約ではなく、慣習に依存します。class Car: def __init__(self, model, speed): #コンストラクタ self._model = model # カプセル化された変数 self._speed = speed # カプセル化された変数 def get_model(self): return self._model def set_speed(self, speed): if speed > 0: self._speed = speed def get_speed(self): return self._speed car_instance = Car("Toyota", 60) print(car_instance.get_model()) # Toyota car_instance.set_speed(80) print(car_instance.get_speed()) # 80
-
-
-
C#:
-
privateを使ったフィールドの非公開と、publicのメソッドの公開:
class Car { private string _model; // カプセル化された変数 public void SetModel(string model) //実際は後述のsetアクセサなどを使います。 { _model = model; } public string GetModel() { return _model; } } class Program { static void Main() { Car carInstance = new Car(); carInstance.SetModel("Toyota"); Console.WriteLine(carInstance.GetModel()); // Toyota } }
説明:
_model
はprivate
で宣言され、直接外部からアクセスできないようになっています。SetModel
メソッドとGetModel
メソッドを通じて、外部からのアクセスが制御されています。
-
staticのフィールドの意味:
class Car { private static int _totalCount; // staticフィールド public Car() { _totalCount++; } public static int TotalCount => _totalCount; // 簡潔なプロパティ } class Program { static void Main() { Car carInstance1 = new Car(); Car carInstance2 = new Car(); Console.WriteLine(Car.TotalCount); // 2 } }
説明:
_totalCount
はstatic
で宣言され、クラスのすべてのインスタンスで共有されるクラス レベルのフィールドです。 -
staticのメソッドの意味とstaticによるステートレスメソッドとステートフルメソッドの違い:
class MathOperations { public int fixedNumber; public static int Add(int a, int b) // staticメソッド { return a + b; } public int Multiply(int a) // 非staticメソッド { return a * fixedNumber; } } class Program { static void Main() { int sum = MathOperations.Add(3, 5); // ステートレスメソッド Console.WriteLine(sum); // 8 MathOperations mathInstance = new MathOperations(); mathInstance.fixedNumber = 4; int product = mathInstance.Multiply(2); // ステートフルメソッド Console.WriteLine(product); // 8 } }
説明:
Add
メソッドはstatic
であり、インスタンスの作成なしに呼び出せるステートレスメソッドです。Multiply
メソッドは非static
であり、インスタンスを介して呼び出す必要があるステートフルメソッドです。
-
コンストラクタの通常の記法と=>演算子を使った簡潔な記法:
class Car { private string model; // 通常のコンストラクタ public Car(string model) { this.model = model; } // 簡潔なコンストラクタ // =>演算子を使用し、1行で簡潔に書ける public Car(string model) => this.model = model; } class Program { static void Main() { Car carInstance1 = new Car("Toyota"); Car carInstance2 = new Car("Honda"); } }
説明:
- 通常のコンストラクタでは
public Car(string model)
の形式で複数行にわたり書かれています。 - 簡潔なコンストラクタでは
=>
演算子を使い、1行で簡潔に書くことができます。 - このように、インスタンスを生成時にフィールド変数に値を渡すことができます。
- 通常のコンストラクタでは
-
静的コンストラクタとは:
class Car { private static int fuelEfficiency; static Car() { // 静的コンストラクタ this.fuelEfficiency = 20; } } class Program { static void Main() { var myCar = new Car(); //ここで、fuelEfficiencyが20.2として作られている。 } }
説明:
- 静的コンストラクタはクラスの初回アクセス時に呼び出され、一度だけ実行されることが保証されます。
-
プロパティとgetアクセサ、setアクセサの記法:
class Car { private string model; public string Model { get { return this.model; } set { if(this.model is string) //_modelに文字列以外は入らない { this.model = value; } } } } class Program { static void Main() { Car carInstance = new Car(); carInstance.Model = "Toyota"; //setアクセサのおかげ Console.WriteLine(carInstance.Model); // Toyota //getアクセサのおかげ } }
説明:
Model
プロパティはget
およびset
アクセサを使用して定義され、外部からのアクセスを制御します。get
アクセサはプロパティの値を取得し、set
アクセサはプロパティの値を設定します。
-
継承
-
継承とは:
継承は、既存のクラスの機能や特性を別のクラスに引き継ぐ仕組みであり、コードの再利用性を高めます。親クラス(または基底クラス)のメンバーを子クラス(または派生クラス)が利用できるようになります。
-
Pythonでの継承方法:
class Animal: def speak(self): return "Animal speaks" class Dog(Animal): # Animalクラスを継承 def bark(self): return "Dog barks" dog_instance = Dog() print(dog_instance.speak()) # Animal speaks print(dog_instance.bark()) # Dog barks
説明:
Dog
クラスがAnimal
クラスを継承しています。Dog
クラスはAnimal
クラスのspeaks
メソッドを利用できます。
-
C#での継承方法:
class Animal { public string Speak() { return "Animal speaks"; } } class Dog : Animal // Animalクラスを継承 { public string Bark() { return "Dog barks"; } } class Program { static void Main() { Dog dogInstance = new Dog(); Console.WriteLine(dogInstance.Speak()); // Animal speaks Console.WriteLine(dogInstance.Bark()); // Dog barks } }
説明:
Dog
クラスがAnimal
クラスを継承しています。そのため、Speak()メソッドもDogクラスは使えます。
-
親クラスのabstractの意味:
abstract class Shape { //Shape()インスタンスは作れない public abstract double Area(); } class Circle : Shape { private readonly double radius; public Circle(double radius) { this.radius = radius; } public override double Area() { //実際の面積の求め方を記述 return Math.PI * this.radius * this.radius; } } class Square : Shape { private readonly double side; public Square(double side) { this.side = side; } public override double Area() //具象メソッド { //実際の面積の求め方を記述 return this.side * this.side; } }
説明:
Shape
クラスがabstract
であり、抽象メソッドArea
を持っています。- 抽象クラスは継承するクラスをグループ化するためだけに作られます。
- 抽象クラス自体は直接インスタンス化できませんが、逆に派生クラスで
Area
メソッドを実装することが必須です。
-
子クラスのコンストラクタでのbase()の意味:
class Vehicle { protected string model; public Vehicle(string model) { this.model = model; } } class Car : Vehicle { private int doors; public Car(string model, int doors) : base(model) { this.doors = doors; } } myCar = new Car("Toyota", 4);
説明:
Car
クラスのコンストラクタでbase(model)
を呼び出すことで、Vehicle
クラスのコンストラクタが実行され、model
が初期化されます。
-
ある関数の引数に親クラスの型を指定している場合でも、継承している子クラスも引数として渡せる:
class Animal { public virtual string Speak() { return "Animal speaks"; } } class Dog : Animal { public override string Speak() { return "Dog barks"; } } class AnimalHandler { public void HandleAnimal(Animal animal) //Animal型の引数のみ受け付ける { Console.WriteLine(animal.Speak()); } } class Program { static void Main() { Dog dogInstance = new Dog(); AnimalHandler.HandleAnimal(dogInstance); // ‘Dog barks’ } }
説明:
AnimalHandler
クラスのHandleAnimal
メソッドはAnimal
型の引数を受け取ります。Dog
クラスはAnimal
クラスを継承しているため、Dog
クラスのインスタンスも渡せます。
-
PythonとC#での多重継承:
Python:
class A: def method_a(self): return "Method A" class B: def method_b(self): return "Method B" class C(A, B): # AとBの両方を継承 def method_c(self): return "Method C"
説明:
- Pythonではクラス宣言時に複数のクラスをカンマで区切って継承します。
- C#では単一継承のみができます。
ここで以下のような関数を考えてみましょう。
using System; class Animal{ //Speak関数のコード } class Dog : Animal { public void Bark() { Console.WriteLine("Dog barks"); } } class Cat : Animal { public void Meow() { Console.WriteLine("Cat meows"); } } class Program { static void ExecuteSpeak(Animal animal) { if (animal is Dog) { Dog dog = (Dog)animal; dog.Bark(); //Bark()はDog型でなければ、ならないため、キャスト式で変換しないと、使えない。 } else if (animal is Cat) { Cat cat = (Cat)animal; cat.Meow(); } else { animal.Speak(); } } static void Main() { Animal dog = new Dog(); Animal cat = new Cat(); ExecuteSpeak(dog); // Output: Dog barks ExecuteSpeak(cat); // Output: Cat meows } }
上記のコードでは、
ExecuteSpeak
関数が引数としてAnimal
型を受け取り、その型に応じてメソッドを実行しています。これにはif文とis演算子、ダウンキャストが使用されています。以下は、同じ機能を持つパターンマッチングを利用したコードです。
class Program { static void ExecuteSpeak(Animal animal) { switch (animal) { case Dog dog: dog.Bark(); break; case Cat cat: cat.Meow(); break; } }//switch文の方がより簡単に書ける。 static void Main() { Animal dog = new Dog(); Animal cat = new Cat(); ExecuteSpeak(dog); // Output: Dog barks ExecuteSpeak(cat); // Output: Cat meows } }
このコードでは
switch
文を使用してパターンマッチングを行い、より簡潔で可読性の高いコードを実現しています。各case
節で直接ダウンキャストされた変数(dog
やcat
)が利用されており、冗長な条件分岐が排除されています。
ポリモーフィズム
ポリモーフィズムの概要:
ポリモーフィズムは、同じ名前のメソッドや関数が異なるクラスや型によって異なる動作をする性質を指します。これにより、プログラムの柔軟性が向上し、同じメソッド名を使用して異なるクラスや型に対して一貫したインターフェースを提供できます。
Pythonでのポリモーフィズム:
Pythonでは、ポリモーフィズムが動的型付けによって容易に実現されます。同じメソッド名を持つ異なるクラスや型に対して、実際の実行時に適切なメソッドが呼び出されます。
class Dog:
def speak(self):
return "Dog barks"
class Cat:
def speak(self):
return "Cat meows"
def execute_speak(animal):
return animal.speak()
dog = Dog()
cat = Cat()
print(execute_speak(dog)) # Output: Dog barks
print(execute_speak(cat)) # Output: Cat meows
上記のPythonコードでは、execute_speak
関数が引数として異なる型のanimal
を受け取り、そのanimal
のspeaks
メソッドを呼び出します。実行時に渡されるオブジェクトの型によって、対応するメソッドが動的に呼び出されます。
C#での抽象メソッドと具象メソッド:
C#では、ポリモーフィズムを実現するために抽象メソッドや仮想メソッドを使用します。抽象メソッドは親クラスで定義され、派生クラスで具体的な実装を提供することが必要です。仮想メソッドはデフォルトの実装を提供し、派生クラスでオーバーライドできます。
using System;
abstract class Animal
{
public abstract string Speak(); // 抽象メソッド
}
class Dog : Animal
{
public override string Speak() //オーバーライドしている(具象メソッド)
{
return "Dog barks";
}
}
class Cat : Animal
{
public override string Speak() //オーバーライドしている(具象メソッド)
{
return "Cat meows";
}
}
class Program
{
static void Main()
{
Animal dog = new Dog();
Animal cat = new Cat();
Console.WriteLine(dog.Speak()); // Output: Dog barks
Console.WriteLine(cat.Speak()); // Output: Cat meows
}
}
上記のC#コードでは、Animal
クラスに抽象メソッドSpeak
があり、Dog
クラスとCat
クラスがそれをオーバーライドしています。Animal
型の変数に異なるサブクラスのオブジェクトを代入し、同じメソッド名を使用してポリモーフィズムを実現しています。
もう一例(再掲)
abstract class Shape
{
public abstract double Area();
}
class Circle : Shape
{
private readonly double radius;
public Circle(double radius)
{
this.radius = radius;
}
public override double Area()
{ //実際の面積の求め方を記述
return Math.PI * this.radius * this.radius;
}
}
class Square : Shape
{
private readonly double side;
public Square(double side)
{
this.side = side;
}
public override double Area() //具象メソッド
{ //実際の面積の求め方を記述
return this.side * this.side;
}
}
このように、Shapeクラスには面積を求めるメソッドがほしいが、すべての子クラスで同じように求められない場合、親クラス側はArea()があることだけを指示しており、子クラス側でどのように実際は計算するかを記述しています。このようにすることで、新たにOvalクラスを作ったとしても、親クラスのShapeクラスを編集する必要はなく、OvalクラスにオーバーライドしたArea()メソッドの中に実装を書き込むだけで機能を実装することができる。新しく追加したクラスのみを触ればよいという構造は、変更容易性においてよいコードといえる。
C#での仮想メソッドとデフォルトの設定:
C#では、virtual
キーワードを使用して仮想メソッドを定義することで、abstract
抽象クラスと同様に子クラスにオーバーライドさせることができます。またvirtual
では、親クラスにそのメソッドのデフォルトの動きを記述できるため、abstract
よりも機能性が高い。これにより、ポリモーフィズムを実現し、基本クラスのメソッドを派生クラスでカスタマイズできます。
以下に、virtual
とoverride
を使用したC#の例を示します。
using System;
class Animal
{
public virtual string Speak()
{
return "Generic animal sound";
}
}
class Dog : Animal
{
public override string Speak()
{
return "Dog barks";
}
}
class Cat : Animal
{
// 仮想メソッドをオーバーライドしない場合、基本クラスのデフォルトの挙動が呼ばれる
}
class Program
{
static void Main()
{
Animal genericAnimal = new Animal();
Animal dog = new Dog();
Animal cat = new Cat();
Console.WriteLine(genericAnimal.Speak()); // Output: Generic animal sound
Console.WriteLine(dog.Speak()); // Output: Dog barks
Console.WriteLine(cat.Speak()); // Output: Generic animal sound (デフォルトの挙動)
}
}
上記の例では、Animal
クラスに仮想メソッドSpeak
があります。Dog
クラスではoverride
を使用してこのメソッドをオーバーライドし、Cat
クラスではオーバーライドせずにデフォルトの挙動を使用しています。プログラム実行時に、オブジェクトの実際の型に基づいて適切なメソッドが呼ばれ、ポリモーフィズムが発揮されます。
インターフェイスについて:
そもそもインターフェイスとは:
インターフェイスは、クラスが実装しなければならないメソッドやプロパティの一連の規約を定義するものです。これにより、異なるクラスが同じメソッドやプロパティを実装することで、共通の振る舞いを持つことができます。インターフェイスはクラスとは異なり、メソッドの実装を持たず、抽象メソッドやプロパティの署名のみを提供します。
C#でのインターフェイス:
C#では、interface
キーワードを使用してインターフェイスを定義します。インターフェイスは、抽象メソッド、プロパティ、イベント、インデクサーなどを含むことができます。クラスがインターフェイスを実装するには、class
キーワードの後にコロン(:)で区切られたインターフェイス名を指定します。
// インターフェイスの定義
public interface IRollable
{
double GetWeight(double density);
//IRollableを実装するクラスは、必ずGetWeight()というメソッドを中に書き込まなければならない。
}
// クラスがインターフェイスを実装
public class Circle : Shape, IRollable
{//Shapeのように親のクラス名がインターフェイス名より先
public double Radius { get; set; }
public double GetWeight(double density)
{
return Math.PI * Radius * Radius * density;
}
}
上記の例では、IRollable
インターフェイスがGetWeight
メソッドの宣言を含んでおり、Circle
クラスがこのインターフェイスを実装しています。
クラスの多重継承はできないが、インターフェイスは複数実装ができる:
C#では、クラスは1つの基底クラスしか持つことができませんが、複数のインターフェイスを実装することができます。これにより、クラスが異なるインターフェイスで定義されたメソッドやプロパティを持つことができます。
public interface IDrawable
{
void Draw();
}
public interface IResizable
{
void Resize(int percentage);
}
public class Rectangle : IDrawable, IResizable
{
public void Draw()
{
// 描画の実装
}
public void Resize(int percentage)
{
// リサイズの実装
}
}
上記の例では、Rectangle
クラスがIDrawable
とIResizable
の両方のインターフェイスを実装しています。
Pythonでのインターフェイス:
Pythonには厳密な型チェックがないため、C#のようなインターフェイスの概念はありません。しかし、Pythonではダック・タイピングが一般的であり、オブジェクトが特定のメソッドや属性を持っているかどうかを確認することで、インターフェイスに近い機能を実現することができます。
class IShape:
def get_area(self):
raise NotImplementedError("Subclasses must implement this method")
class Circle(IShape):
def __init__(self, radius):
self.radius = radius
def get_area(self):
return 3.14 * self.radius * self.radius
上記の例では、IShape
クラスがインターフェイスのような機能を提供していますが、これはあくまで慣習的なものであり、実行時に型が確認されるわけではありません。
コードファイル設計について:
Pythonでのコードファイル分けの適切な設計:
Pythonではモジュール(ファイル)ごとに関連するクラスや関数をまとめ、関連性の高いものを同じディレクトリに配置することが一般的です。各モジュールは単一の責務を持つように設計され、複数のモジュールが相互に依存し合うことで全体の機能を構築します。
例えば、以下はPythonのプロジェクト構造の一例です。
project/
|-- main.py
|-- utils/
| |-- helper.py
|-- models/
| |-- user.py
この例では、main.py
がエントリーポイントとなり、utils
ディレクトリにはプロジェクト全体で使用されるヘルパーモジュールが、models
ディレクトリにはデータモデルが格納されています。
C#での名前空間とファイル分け、トップレベルステートメントなどの適切な定義の仕方:
C#では、クラスや関数を名前空間に包むことで、コードの整理が行われます。ファイル分けも可能であり、一つのファイルに一つのクラスを持たせることが一般的です。トップレベルステートメントを使用することで、ファイルの先頭で直接コードを記述できます。
例えば、以下はC#のプロジェクト構造の一例です。
// main.cs
using System;
namespace MyApp
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
}
この例では、MyApp
が名前空間であり、Program
クラスがエントリーポイントとなります。
slnファイル、csprojファイル, binフォルダ、objフォルダとは:
sln
ファイル: Visual Studioのソリューションファイルで、プロジェクトをまとめて管理するためのものです。csproj
ファイル: C#プロジェクトファイルで、プロジェクトの設定や参照するファイル・アセンブリを定義します。bin
フォルダ: プロジェクトのビルド結果(バイナリ)が出力される場所です。obj
フォルダ: ビルド中に生成される中間ファイルが格納される場所で、ビルドに関する情報が保持されます。
これらのファイルやフォルダはビルドやプロジェクトの管理に関わるものであり、通常開発者が直接編集することはありません。
補足事項
C#のコードのエントリーポイントとMain(args)のargsとは:
C#のコンソールアプリケーションでは、Main
メソッドがエントリーポイントとなります。Main
メソッドは通常、文字列の配列(string[] args
)を引数として受け取ります。これにはプログラムが実行される際に渡されるコマンドライン引数が含まれます。
class Program
{
static void Main(string[] args)
{
// argsにはコマンドライン引数が格納される
}
}
internal修飾子について:
internal
修飾子は、同じアセンブリ内でのみアクセス可能なメンバを指定します。他のアセンブリからはアクセスできません。これは、モジュール内での情報の隠蔽や、アセンブリ内のコンポーネント間での安全な通信を可能にします。
暗黙的global usingディレクティブとは:
C# 10からは、プロジェクト全体で使用される共通の名前空間や型を毎回using
で宣言する手間を省くために、global using
ディレクティブが導入されました。これ
により、プロジェクト全体で共通の名前空間を宣言することができます。
global using System;
global using MyCommonNamespace;
// これ以降のコードでSystemやMyCommonNamespaceの型を個別にusingする必要がなくなる
これにより、コード全体で共通して利用される名前空間を一括で指定できます。
まとめ
この「C#をPythonと比較して学ぶ」シリーズの最終回では、C#とPythonの様々な側面を比較し、プログラミングの基本原則やオブジェクト指向の概念に焦点を当てました。
最初に、ライブラリの活用について触れました。ライブラリは両言語においてコードの再利用を促進し、効率的な開発を支援します。C#ではusingディレクティブを使用し、Pythonではimport文を通じて外部ライブラリを取り込みました。
次に、クラスとメソッドによる部品化に焦点を当て、プログラムの構造を整理する手法としてこれらの概念の重要性を探りました。クラスとメソッドはコードを可読性高く、変更容易で再利用可能な形に整備する助けとなります。
その後、オブジェクト指向プログラミングの基本概念であるカプセル化、継承、ポリモーフィズムに焦点を当てました。これらの概念はクラスとオブジェクトを通じてソフトウェアの設計を行う上で重要な手段です。クラスを使用してデータと処理を組み合わせ、柔軟かつ効果的なコードを構築しました。
最後に、インターフェイスの概念に触れました。インターフェイスは抽象メソッドやプロパティの規約を提供し、クラスがこれを実装することで一貫性のある振る舞いを定義します。これにより、異なるクラスが同じインターフェイスを実装することで、共通の操作を行う手段が整備されます。
このシリーズを通じて、C#とPythonの比較を通してプログラミングの基本を理解し、それぞれの言語が提供する特長や適切な使用ケースを把握しました。プログラミング言語の選択や概念の理解において、これらの知識が有益であることを期待します。
注:なお、私がC#について学んだことを元にしていますが、不足している部分や誤っている部分があるかもしれません。その際は、遠慮なくご指摘いただければ幸いです。