はじめに
自分は C 言語、C++ 、Delphi 、VB.NET 、最近は JavaScript でプログラミングしています。
C 系言語や VB.NET を使っているので、C# のソースコードも読めなくはなかったのですが、
.NET MAUI や Blazor を始めたので、C# のコーディングの「おさらい」することにしました。
C# をおさらいしてみた
例題①
レイアウト規則や命名規則
参考:C# のコーディング規則 | Microsoft Learn
class Person
{
public Guid Id;
public string Name;
}
static void Main(string[] args)
{
Person person = new Person();
person.Id = Guid.NewGuid();
person.Name = "....";
}
プロパティの実装
参考:プロパティ - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
class Person
{
private int _age;
public int Age
{
get { return _age; }
set { _age = value; }
}
以下略
単純なゲッタとセッタを簡単に実装できます。↓
class Person
{
public int Age { get; set; }
以下略
オブジェクトの初期化
参考:オブジェクト初期化子とコレクション初期化子 - C# プログラミング ガイド | Microsoft Learn
Person person = new Person()
{
Id = Guid.NewGuid(),
Name = ".....",
};
Person[] persons =
{
new Person()
{
Id = Guid.NewGuid(), Name = "....."
},
new Person()
{
Id = Guid.NewGuid(), Name = "....."
},
};
暗黙の型指定
参考:C#のvarの使い方を解説。暗黙の型指定とは何か? | .NETコラム
var person = new Person()
{
Id = Guid.NewGuid(),
Name = ".....",
};
Person p = new Person()
って、クラス名が 2 箇所に書いてあってくどいですね。これがすっきり書けます。VB.NET も、Dim p As Person = new Person()
が Dim p As New Person()
って書けますね。
例題②
値型と参照型
参考:値型と参照型 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
struct Test
{
public int Number;
}
static void Main(string[] args)
{
Test a = new Test();
a.Number = 1;
Test b = a;
b.Number = 2;
Console.WriteLine($"a.Number:{a.Number}, b:Number:{b.Number}");
Console.WriteLine($"a.Equals(b):{a.Equals(b)}");
}
変数 a
と b
は独立しています。↑
class Test
{
public int Number;
}
static void Main(string[] args)
{
Test a = new Test();
a.Number = 1;
Test b = a;
b.Number = 2;
Console.WriteLine($"a.Number:{a.Number}, b:Number:{b.Number}");
Console.WriteLine($"a.Equals(b):{a.Equals(b)}");
}
変数 a
と b
はポインタで、同じ領域を指しています。↑
参考:【C#】「new演算子」メモリを確保してオブジェクトを作成し表示する | Cプロ
参考にしたソースコードで、構造体の変数なのに new
していて、C++ を使っていた自分は面食らいました。
値渡しと参照渡し
参考:値渡しと参照渡し - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
struct Test
{
public int Number;
}
static void Main(string[] args)
{
Test c = new Test();
c.Number = 0;
Method(c);
Console.Write($"c.Number:{c.Number}");
}
static void Method(Test x)
{
Console.WriteLine($"x.Number:{x.Number}");
x.Number = 9;
}
class Test
{
public int Number;
}
以下略
変数 c
と x
は独立しています。↑
struct Test
{
public int Number;
}
static void Main(string[] args)
{
Test c = new Test();
c.Number = 0;
Method(ref c);
Console.Write($"c.Number:{c.Number}");
}
static void Method(ref Test x)
{
Console.WriteLine($"x.Number:{x.Number}");
x.Number = 9;
}
class Test
{
public int Number;
}
以下略
変数 x
はポインタで、c
と同じ領域を指しています。↑
前略
Test c;
Method(out c);
Console.Write($"c.Number:{c.Number}");
}
static void Method(out Test x)
{
x = new Test();
x.Number = 9;
}
ref
は関数に渡す前に new
するが、out
はポインタ変数を渡して関数で new
します。↑
例題③
継承
参考:継承 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
public class Person
{
public string Name { get; set; }
public void SayName()
{
Console.WriteLine("名前は {0} です", this.Name);
}
}
public class Student : Person
{
public void Study()
{
Console.WriteLine("勉強します");
}
}
public class Intern : Student
{
public void DoOJT()
{
Console.WriteLine("研修します");
}
}
static void Main(string[] args)
{
Intern i = new Intern();
i.Name = ".....";
i.SayName();
i.Study();
i.DoOJT();
}
仮想メソッドと再定義(オーバーライド)
public class Student : Person
{
public virtual void Study()
{
Console.WriteLine("勉強します");
}
}
public class Intern : Student
{
public override void Study()
{
Console.WriteLine("練習します");
}
}
static void Main(string[] args)
{
Student s = new Student();
s.Study();
Intern i = new Intern();
i.Study();
}
親クラスでメソッドに virtual
を指定して、子クラスで override
して同じ名前で内容を変更します。↑
参考:[多態性 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C]
(https://ufcpp.net/study/csharp/oo_polymorphism.html)
抽象メソッドと再定義
参考:抽象メソッド、抽象クラス - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
public abstract class Student : Person
{
public abstract void Study();
}
public class Intern : Student
{
public override void Study()
{
Console.WriteLine("練習します");
}
}
static void Main(string[] args)
{
Student s = new Student();
以下略
上記の Student s = new Student()
はエラーになります。
前略
Intern i = new Intern();
i.Study();
後略
このコードは実行できます。
インターフェース
参考:インターフェース - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
public interface ICodable
{
void Coding();
}
public class Programmer : ICodable
{
public void Coding()
{
Console.WriteLine("コーディングします");
}
}
以下略
インターフェース ICodable
を継承したクラス Programmer
で、メソッド Coding()
を実装しないとエラーになります。
例題④
配列
参考:配列 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
var a = new string[3];
a[0] = "...1";
a[1] = "...2";
a[2] = "...3";
var s = a[1];
Console.WriteLine(s);
public class Person
{
public string Name { get; set; }
}
static void Main(string[] args)
{
var a = new Person[3];
a[0] = new Person(){ Name = "...1" };
a[1] = new Person(){ Name = "...2" };
a[2] = new Person(){ Name = "...3" };
var p = a[1];
Console.WriteLine(p.Name);
}
リスト
var a = new ArrayList();
a.Add("...1");
a.Add("...2");
a.Add("...3");
a.Insert(1, "...9");
var s = (string)a[1];
Console.WriteLine(s);
前略
var a = new ArrayList();
a.Add(new Person(){ Name = "...1" });
a.Add(new Person(){ Name = "...2" });
a.Add(new Person(){ Name = "...3" });
a.Insert(1, new Person(){ Name = "...9" });
var p = (Person)a[1];
Console.WriteLine(p.Name);
参考:ジェネリック - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
var list = new List<string>();
list.Add("...1");
list.Add("...2");
list.Add("...3");
list.Insert(1, "...9");
var item = list[1];
Console.WriteLine(item);
前略
foreach (var item in list)
{
Console.WriteLine(item);
}
ディクショナリ
var dict = new Dictionary<string, string>();
dict.Add("key1", "...1");
dict.Add("key2", "...2");
dict.Add("key3", "...3");
var value = dict["key2"];
Console.WriteLine(value);
前略
foreach (var pair in dict)
{
Console.WriteLine("{0}:{1}", pair.Key, pair.Value);
}
例題⑤
ラムダ式
参考:ラムダ式 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
ラムダ式というかアロー関数って苦手なんですよね。JavaScript でも無名関数は function()
を使って書いています。でもそろそろ慣れないといけないかな。
LINQ
参考:LINQ - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
LINQ って機能があるのは知っていたけれど無視していました。ところが JavaScript で filter()
や sort()
は便利に使っていました。LINQ の Where()
や OrderBy()
って同じなんですね。
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
static void Main(string[] args)
{
var persons = new List<Person>
{
new Person { Name = "...1", Age = 25 },
new Person { Name = "...2", Age = 15 },
new Person { Name = "...3", Age = 10 },
};
var list = persons
.Where((p) => p.Age <= 20)
.OrderBy((p) => p.Age);
foreach (var person in list)
{
Console.WriteLine("{0} {1}", person.Name, person.Age.ToString());
}
}
Where()
や OrderBy()
の引数にラムダ式が使われています。これに抵抗あって、LINQ を避けてきたんですね。これをデリゲートで書き直してみます。↓
前略
var list = persons
.Where(delegate(Person p) { return p.Age <= 20; })
.OrderBy(delegate(Person p) { return p.Age; });
後略
自分には、これがしっくりきます。でも、こんなコードは見たことないですね。↑
LINQ は以下の書き方もできるそうです。↓
前略
var list = from p in persons
where p.Age <= 20
orderby p.Age;
後略
例題⑥
例外処理
参考:例外処理 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
string s = "x";
int result;
try
{
result = int.Parse(s);
}
catch (Exception ex) {
Console.WriteLine(ex.Message);
}
上記のコードは Parse()
で例外が発生して catch
されます。
前略
try
{
result = int.Parse(s);
}
catch (ArgumentNullException ex) // NULL のとき
{
Console.WriteLine(ex.Message);
}
catch (FormatException ex) // 数字でない文字のとき
{
Console.WriteLine(ex.Message);
}
catch (Exception ex) // それ以外
{
Console.WriteLine(ex.Message);
}
Tester-Doer パターン
参考:[雑記] 例外の使い方 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
if (string.IsNullOrEmpty(s))
{
Console.WriteLine("空文字は数値にできません");
}
else if (!s.All(char.IsDigit))
{
Console.WriteLine("数字でない文字列です");
}
else try
{
result = int.Parse(s);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
if (!int.TryParse(s, out result))
{
Console.WriteLine("数値に変換できません");
}
例外の再スロー
int Convert(string s)
{
return int.Parse(s);
}
前略
try
{
result = Convert(s);
}
catch (Exception ex) {
Console.WriteLine(ex.Message);
}
Convert()
内で発生した例外が、呼出した側で catch
されます。↑
int Convert(string s)
{
try
{
return int.Parse(s);
}
catch (Exception ex)
{
return 0; // 0 を返す
}
}
前略
try
{
result = Convert(s);
}
catch (Exception ex) {
Console.WriteLine(ex.Message);
}
Convert()
内で発生した例外が関数内で処理されて、呼出した側で catch
されません。↑
int Convert(string s)
{
try
{
return int.Parse(s);
}
catch (Exception ex)
{
throw ex; // 例外を再スローする
}
}
前略
try
{
result = Convert(s);
}
catch (Exception ex) {
Console.WriteLine(ex.Message);
}
例題⑦
リソースの破棄
参考:リソースの破棄 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
var fs = new FileStream(@"***.txt", FileMode.Open, FileAccess.Read);
var sr = new StreamReader(fs, Encoding.UTF8);
var buf = sr.ReadLine();
Console.WriteLine(buf);
sr.Dispose();
fs.Dispose();
FileStream
や StreamReader
は、確保したリソースを使用し終わったところで破棄します。
Close()
しなくていいのかなと気になりました。↓
参考:[雑記] Dispose にまつわる余談 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
例外が発生したとき漏れなく Dispose()
するように処理を書きます。↓
try {
var fs = new FileStream(@"***.txt", FileMode.Open, FileAccess.Read);
try {
var sr = new StreamReader(fs, Encoding.UTF8);
try {
var buf = sr.ReadLine();
Console.WriteLine(buf);
sr.Dispose(); // 正常処理のとき
}
catch {
sr.Dispose(); // 例外処理のとき
}
fs.Dispose(); // 正常処理のとき
}
catch {
fs.Dispose(); // 例外処理のとき
}
}
catch (Exception ex) {
Console.WriteLine(ex.Message);
}
正常処理のときと例外処理のときと両方を書くのでくどくなっています。↑
try {
var fs = new FileStream(@"***.txt", FileMode.Open, FileAccess.Read);
try {
var sr = new StreamReader(fs, Encoding.UTF8);
try {
var buf = sr.ReadLine();
Console.WriteLine(buf);
}
finally {
sr.Dispose();
}
}
finally {
fs.Dispose();
}
}
catch (Exception ex) {
Console.WriteLine(ex.Message);
}
finally
を使うことでダブりがなくせました。↑
try {
using (var fs = new FileStream(@"***.txt", FileMode.Open, FileAccess.Read))
{
using (var sr = new StreamReader(fs, Encoding.UTF8))
{
var buf = sr.ReadLine();
Console.WriteLine(buf);
}
}
}
catch (Exception ex) {
Console.WriteLine(ex.Message);
}
using
を使うと Dispose()
を明記しなくても済みます。↑
try {
using (var fs = new FileStream(@"***.txt", FileMode.Open, FileAccess.Read))
using (var sr = new StreamReader(fs, Encoding.UTF8))
{
var buf = sr.ReadLine();
Console.WriteLine(buf);
}
}
catch (Exception ex) {
Console.WriteLine(ex.Message);
}
こんな書き方もできます。さらに短く書けました。↑
例題⑧
シリアル化(シリアライズ)
参考:[C#][.NET][XML] オブジェクトのシリアライズ/デシリアライズ(DataContractSerializerの使用方法) - Qiita
class Person
{
public string Name { get; set; }
public List<string> Hobbies { get; set; }
}
static void Main(string[] args)
{
var p = new Person()
{
Name = "....",
Hobbies = new List<string>() { "....", "...." }
};
後略
変数 p
の内容をファイルに保存したいと思います。↓
[DataContract]
class Person
{
[DataMember]
public string Name { get; set; }
[DataMember]
public List<string> Hobbies { get; set; }
}
static void Main(string[] args)
{
var p = new Person()
{
Name = "....",
Hobbies = new List<string>() { "....", "...." }
};
using (var f = File.Open(@"***.xml", FileMode.Create))
{
var s = new DataContractSerializer(typeof(Person));
s.WriteObject(f, p);
}
}
XML 形式で保存されます。↑
XML ファイルから読込したいと思います。↓
前略
static void Main(string[] args)
{
Person p;
using (var f = File.Open(@"***.xml", FileMode.Open))
{
var s = new DataContractSerializer(typeof(Person));
p = (Person)s.ReadObject(f);
}
}
JSON 形式で保存したり読込したいと思います。↓
前略
using (var f = File.Open(@"***.json", FileMode.Create))
{
var s = new DataContractJsonSerializer(typeof(Person));
s.WriteObject(f, p);
}
前略
using (var f = File.Open(@"***.json", FileMode.Open))
{
var s = new DataContractJsonSerializer(typeof(Person));
p = (Person)s.ReadObject(f);
}
例題⑨
非同期処理
参考:連載:C# 5.0&VB 11.0新機能「async/await非同期メソッド」入門 - @IT
Windows アプリで、ボタン押下するとボタンを使用不可にして、処理が終わると使用可能に戻します。↓
前略
private void Button_Click(object sender, RoutedEventArgs e)
{
this.button.IsEnabled = false;
Thread.Sleep(3000); // 何か長い処理
this.button.IsEnabled = true;
}
これでは、長い処理している間にアプリが反応しなくなります。↑
.NET 1.0 から使用できる Thread クラスを使用します。↓
private void Button_Click(object sender, RoutedEventArgs e)
{
this.button.IsEnabled = false;
var thread = new Thread(() =>
{
Thread.Sleep(3000);
// Dispatcher を利用してUIスレッドに処理を配送
this.Dispatcher.BeginInvoke((Action)(() =>
{
this.button.IsEnabled = true;
}));
});
thread.Start(); // 別スレッドの処理開始
}
非同期プログラミングモデル(APM)で書いてみます。↓
private void Button_Click(object sender, RoutedEventArgs e)
{
this.button.IsEnabled = false;
var method = new Func<double>(() =>
{
Thread.Sleep(3000);
return 0; // 結果を返せる
});
method.BeginInvoke(ar =>
{
var result = method.EndInvoke(ar); // 結果を貰える
this.Dispatcher.BeginInvoke((Action)(() =>
{
this.button.IsEnabled = true;
}));
}, null);
}
.NET 2.0 から使えるイベントベース非同期パターン(EAP)で書いてみます。↓
private readonly BackgroundWorker worker = new BackgroundWorker();
public MainWindow()
{
this.InitializeComponent();
// 先に非同期処理の本体や完了時処理を登録しておく
this.worker.DoWork += this.OnDoWork;
this.worker.RunWorkerCompleted += this.OnCompleted;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.button.IsEnabled = false;
this.worker.RunWorkerAsync(); // 非同期処理開始
}
// 非同期処理本体
private void OnDoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(3000);
e.Result = 0; // 結果を返せる
}
// UIスレッド上で動作する完了時コールバック
private void OnCompleted(object sender, RunWorkerCompletedEventArgs e)
{
var result = (double)e.Result; // 結果を貰える
this.button.IsEnabled = true;
}
.NET 4 で追加された Task クラスを使って、タスクベース非同期パターン(TAP)で書いてみます。↓
private void Button_Click(object sender, RoutedEventArgs e)
{
this.button.IsEnabled = false;
Task.Factory.StartNew(() => Thread.Sleep(3000))
.ContinueWith(_ =>
{
this.button.IsEnabled = true;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
.NET 4.5 から使用できる async
と await
を使います。↓
private async void Button_Click(object sender, RoutedEventArgs e)
{
this.button.IsEnabled = false;
await Task.Run(() => Thread.Sleep(3000));
this.button.IsEnabled = true;
}
async
と await
については、こんな記事がありました。↓