M.Hiroi's Home Page

C# Programming

お気楽C#プログラミング超入門

[ Home | C# ]

C# の基礎知識 (後編)

●イテレータ

リスト : イテレータの簡単な例題 (sample12/Program.cs)

using System;
using System.Collections;
using System.Collections.Generic;

class Foo : IEnumerable {
  int a, b, c;
  public Foo(int x, int y, int z) {
    a = x;
    b = y;
    c = z;
  }

  public IEnumerator GetEnumerator() {
    yield return a;
    yield return b;
    yield return c;
  }
}

class Bar<T> : IEnumerable<T> {
  T a, b, c;
  public Bar(T x, T y, T z) {
    a = x;
    b = y;
    c = z;
  }

  public IEnumerator<T> GetEnumerator() {
    yield return a;
    yield return b;
    yield return c;
  }

  // public は付けない
  IEnumerator IEnumerable.GetEnumerator() {
    return this.GetEnumerator();
  }
}

class Test {
  static void Main() {
    var a = new Foo(1, 2, 3);
    foreach(int n in a) {
      Console.WriteLine("{0}", n);
    }

    var b = new Bar<int>(10, 20, 30);
    foreach(int n in b) {
      Console.WriteLine("{0}", n);
    }
  }
}
$ dotnet run --project sample12
1
2
3
10
20
30

●インデクサー

リスト : インデクサーの簡単な例題

using System;

class Foo {
  int a, b, c;
  public Foo(int x, int y, int z) {
    a = x;
    b = y;
    c = z;
  }

  public int this[int n] {
    set {
      switch(n) {
      case 0: a = value; break;
      case 1: b = value; break;
      default: c = value; break;
      }
    }
    get {
      switch(n) {
      case 0: return a;
      case 1: return b;
      default: return c;
      }
    }
  }
}

class Test {
  static void Main() {
    var a = new Foo(1, 2, 3);
    for (int i = 0; i < 3; i++) {
      a[i] *= 10;
      Console.WriteLine("{0}", a[i]);
    }
  }
}
$ dotnet run --project sample13
10
20
30

●LINQ

$ dotnet script
> var a = new int[8] {1,2,3,4,5,6,7,8};
> a.Select(n => n * n)
Enumerable.SelectArrayIterator<int, int>(Count = 8)
> a.Select(n => n * n).ToList()
List<int>(8) { 1, 4, 9, 16, 25, 36, 49, 64 }
> a.Select(n => n * 1.5).ToList()
List<double>(8) { 1.5, 3, 4.5, 6, 7.5, 9, 10.5, 12 }
> a.Where(n => n % 2 == 0).ToList()
List<int>(4) { 2, 4, 6, 8 }
> a.Where(n => n % 2 != 0).ToList()
List<int>(4) { 1, 3, 5, 7 }
> a.Aggregate((sum, n) => sum + n)
36
> a.Aggregate(100, (sum, n) => sum + n)
136

> var b = new int[] {5,6,4,7,3,8,2,9,1,0};
> b.OrderBy(n => n)
OrderedEnumerable<int, int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
> b.OrderBy(n => n).ToList()
List<int>(10) { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
> b.OrderByDescending(n => n).ToList()
List<int>(10) { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }
> b.First(n => n % 2 == 0)
6
> b.Last(n => n % 2 != 0)
1

> var c = new int[] {2,4,6,8,10};
> c.All(n => n % 2 == 0)
true
> c.Any(n => n % 2 != 0)
false

> var d = new [] { new {name="foo", gr=1}, new {name="bar", gr=2},
* new {name="baz", gr=1}, new {name="oops", gr=2}};
> d
<>f__AnonymousType0#19<string, int>[4] { \{ name = "foo", gr = 1 }, \{ name = "bar", gr = 2 }, 
\{ name = "baz", gr = 1 }, \{ name = "oops", gr = 2 } }
> d.GroupBy(n => n.gr)
GroupedEnumerable<<>f__AnonymousType0#19<string, int>, int> { Key = 1 { Key=1, =\{ name = "foo", gr = 1 }, 
=\{ name = "baz", gr = 1 } }, Key = 2 { Key=2, =\{ name = "bar", gr = 2 }, =\{ name = "oops", gr = 2 } } }
> foreach (var xs in d.GroupBy(n => n.gr)) {
* Console.WriteLine(xs.Key);
* foreach (var item in xs) {
* Console.WriteLine(item.name);
* }
* }
1
foo
baz
2
bar
oops

●LINQ (クエリ式)

> b
int[10] { 5, 6, 4, 7, 3, 8, 2, 9, 1, 0 }
> from x in b where x % 2 == 0 select x
Enumerable.WhereArrayIterator<int> { 6, 4, 8, 2, 0 }
> from x in b where x % 2 == 0 orderby x select x
OrderedEnumerable<int, int> { 0, 2, 4, 6, 8 }
> var xs = new[] { new {name="foo", score=10}, new {name="bar", score=20},
*  new {name="baz", score=40}, new {name="oops", score=30}};
> from x in xs where x.score > 20 select x.name
Enumerable.WhereSelectArrayIterator<<>f__AnonymousType0#35<string, int>, string> { "baz", "oops" }
> from x in xs where x.score > 20 select x
Enumerable.WhereArrayIterator<<>f__AnonymousType0#35<string, int>> { \{ name = "baz", score = 40 }, 
\{ name = "oops", score = 30 } }
リスト : クエリ式の簡単な使用例 (sample14/Program.cs)

using System;
using System.Linq;

class Test {
  static void Main() {
    var heightTable = new [] {
      new {id=1,  name="Ada",    height=148.7, rank=1},
      new {id=2,  name="Alice",  height=149.5, rank=2},
      new {id=3,  name="Carey",  height=133.7, rank=3},
      new {id=4,  name="Ellen",  height=157.9, rank=4},
      new {id=5,  name="Hanna",  height=154.2, rank=1},
      new {id=6,  name="Janet",  height=147.8, rank=2},
      new {id=7,  name="Linda",  height=154.6, rank=3},
      new {id=8,  name="Maria",  height=159.1, rank=4},
      new {id=9,  name="Miranda",height=148.2, rank=1},
      new {id=10, name="Sara",   height=153.1, rank=2},
      new {id=11, name="Tracy",  height=138.2, rank=3},
      new {id=12, name="Violet", height=138.7, rank=4},
    };
    var xs = from x in heightTable where x.height < 140
             orderby x.height select new {x.name, x.height};
    foreach(var x in xs) {
      Console.WriteLine("{0}, {1}", x.name, x.height);
    }
    var ys = from x in heightTable where x.height > 150
             orderby x.height descending select new {x.name, x.height};
    foreach(var y in ys) {
      Console.WriteLine("{0}, {1}", y.name, y.height);
    }
    var gs = from x in heightTable
      group new { x.name, x.height } by x.rank;
    foreach(var g in gs) {
      Console.Write("{0}: ", g.Key);  // プロパティ Key でキーの値を取得できる
      foreach(var p in g) {
        Console.Write("({0}, {1}) ", p.name, p.height);
      }
      Console.WriteLine("");
    }
  }
}
$ dotnet run --project sample14
Carey, 133.7
Tracy, 138.2
Violet, 138.7
Maria, 159.1
Ellen, 157.9
Linda, 154.6
Hanna, 154.2
Sara, 153.1
1: (Ada, 148.7) (Hanna, 154.2) (Miranda, 148.2)
2: (Alice, 149.5) (Janet, 147.8) (Sara, 153.1)
3: (Carey, 133.7) (Linda, 154.6) (Tracy, 138.2)
4: (Ellen, 157.9) (Maria, 159.1) (Violet, 138.7)

●ファイル入出力

リスト : テキストファイルの連結 (cat/Program.cs)

using System;
using System.IO;

class Test {
  static void Main(string[] args) {
    foreach(string name in args) {
      using (var s = new StreamReader(name)) {
        string? buff;
        while ((buff = s.ReadLine()) != null) {
          Console.WriteLine(buff);
        }
      }
    }
  }
}
$ ls
Program.cs  cat.csproj  obj
$ dotnet run Program.cs
using System;
using System.IO;

class Test {
  static void Main(string[] args) {
    foreach(string name in args) {
      using (var s = new StreamReader(name)) {
        string? buff;
        while ((buff = s.ReadLine()) != null) {
          Console.WriteLine(buff);
        }
      }
    }
  }
}
リスト : テキストファイルのコピー (cp/Program.cs)

using System;
using System.IO;

class Test {
  static void Main(string[] args) {
    if (args.Length < 2) {
      Console.WriteLine("arguments error");
    } else {
      using (var sIn = new StreamReader(args[0])) {
        using (var sOut = new StreamWriter(args[1])) {
          string? buff;
          while ((buff = sIn.ReadLine()) != null) {
            sOut.WriteLine(buff);
          }
        }
      }
    }
  }
}
$ ls
Program.cs  cp.csproj  obj
$ dotnet run Program.cs test.txt
$ ls
Program.cs  bin  cp.csproj  obj  test.txt
$ cat test.txt
using System;
using System.IO;

class Test {
  static void Main(string[] args) {
    if (args.Length < 2) {
      Console.WriteLine("arguments error");
    } else {
      using (var sIn = new StreamReader(args[0])) {
        using (var sOut = new StreamWriter(args[1])) {
          string? buff;
          while ((buff = sIn.ReadLine()) != null) {
            sOut.WriteLine(buff);
          }
        }
      }
    }
  }
}

●バイナリファイルの入出力

リスト : バイナリファイルのコピー (copy/Program.cs)

using System;
using System.IO;

class Test {
  static void Main(string[] args) {
    if (args.Length < 2) {
      Console.WriteLine("arguments error");
    } else {
      using (var sIn = new FileStream(args[0], FileMode.Open);     // using 宣言, C# 8.0 から利用可能
      using (var sOut = new FileStream(args[1], FileMode.Create);
      int c;
      while ((c = sIn.ReadByte()) != -1) {
        sOut.WriteByte((byte)c);
      }
    }
  }
}
$ ls copy
Program.cs  copy.csproj  obj
$ cd copy
$ dotnet run Program.cs test.txt
$ ls
Program.cs  bin  copy.csproj  obj  test.txt
$ cat test.txt
using System;
using System.IO;

class Test {
  static void Main(string[] args) {
    if (args.Length < 2) {
      Console.WriteLine("arguments error");
    } else {
      using var sIn = new FileStream(args[0], FileMode.Open);
      using var sOut = new FileStream(args[1], FileMode.Create);
      int c;
      while ((c = sIn.ReadByte()) != -1) {
        sOut.WriteByte((byte)c);
      }
    }
  }
}
リスト : 乱数 (double) データの読み書き (sample23a/Program.cs)

using System;
using System.IO;

class Test {
  static void Main(string[] args) {
    {
      using var s1 = new FileStream("test.dat", FileMode.Create);
      using var s2 = new BinaryWriter(s1);
      var r = new Random();
      Console.WriteLine("----- write random data -----");
      for (int i = 0; i < 10; i++) {
        double x = r.NextDouble();
        Console.WriteLine(x);
        s2.Write(x);
      }
    }
    using var s3 = new FileStream("test.dat", FileMode.Open);
    using var s4 = new BinaryReader(s3);
    Console.WriteLine("----- read random data -----");
    for (int i = 0; i < 10; i++) {
      double x = s4.ReadDouble();
      Console.WriteLine(x);
    }
  }
}
$ dotnet run --project sample23a
----- write random data -----
0.004021309139920826
0.1083248895192791
0.1777151122192152
0.48411280991540695
0.49638943134720204
0.13615221317311488
0.6281558086366923
0.928947103389078
0.599753063548463
0.5356192240569689
----- read random data -----
0.004021309139920826
0.1083248895192791
0.1777151122192152
0.48411280991540695
0.49638943134720204
0.13615221317311488
0.6281558086366923
0.928947103389078
0.599753063548463
0.5356192240569689

●名前空間

リスト : 名前空間の使用例 (sample15/Program.cs)

using System;
using Foo;
using Foo.Bar;

namespace Foo {
  namespace Bar {
    class Baz {}
  }
  class Oops {}
}

class Test {
  static void Main() {
    var a = new Foo.Bar.Baz();
    var b = new Foo.Oops();
    Console.WriteLine("{0}", a);
    Console.WriteLine("{0}", b);

    var c = new Baz();
    var d = new Oops();
    Console.WriteLine("{0}", c);
    Console.WriteLine("{0}", d);
  }
}
$ dotnet run --project sample15
Foo.Bar.Baz
Foo.Oops
Foo.Bar.Baz
Foo.Oops

●ライブラリの作成

$ dotnet new console -o sample16
テンプレート "コンソール アプリ" が正常に作成されました。

作成後の操作を処理しています...
/home/mhiroi/csharp/sample16/sample16.csproj で ' dotnet restore ' を実行しています...
  復元対象のプロジェクトを決定しています...
  /home/mhiroi/csharp/sample16/sample16.csproj を復元しました (141 ms)。
正常に復元されました。

$ cd sample16
$ dotnet new classlib -o hello
テンプレート "クラス ライブラリ" が正常に作成されました。

作成後の操作を処理しています...
/home/mhiroi/csharp/sample16/hello/hello.csproj で ' dotnet restore ' を実行しています...
  復元対象のプロジェクトを決定しています...
  /home/mhiroi/csharp/sample16/hello/hello.csproj を復元しました (104 ms)。
正常に復元されました。

$ ls hello
Class1.cs  hello.csproj  obj
リスト : 簡単なライブラリの作成 (sample16/hello/Class1.cs)

using System;

namespace hello;

public class HelloWorld
{
  public static void Greeting() {
    Console.WriteLine("Hello World");
  }
}
$ dotnet new sln
テンプレート "ソリューション ファイル" が正常に作成されました。
$ ls
Program.cs  hello  obj  sample16.csproj  sample16.sln
$ dotnet sln add ./hello/hello.csproj
プロジェクト `hello/hello.csproj` をソリューションに追加しました。
リスト : ライブラリの使用例 (sample16/Program.cs)

using hello;

class Test {
  static void Main() {
    HelloWorld.Greeting();
  }
}
$ dotnet run
Hello World

●値呼びと参照呼び

リスト : 値の交換 (sample17/Program.cs)

using System;

class Test {
  static void Swap(ref int a, ref int b) {
    int c = a;
    a = b;
    b = c;
  }

  static void Main() {
    int a = 1, b = 2;
    Console.WriteLine("a = {0}, b = {1}", a, b);
    Swap(ref a, ref b);
    Console.WriteLine("a = {0}, b = {1}", a, b);
  }
}
$ dotnet run --project sample17
a = 1, b = 2
a = 2, b = 1
リスト : out の使い方 (sample18/Program.cs)

using System;

class Test {
  // 商と剰余を返す (Math.DivRem() と同じ)
  static int DivRem(int x, int y, out int z) {
    z = x % y;
    return x / y;
  }

  static void Main() {
    int r;  // 初期化しなくてもよい
    int q = DivRem(11, 4, out r);
    Console.WriteLine("{0}, {1}", q, r);
  }
}
$ dotnet run --project sample18
2, 3

●データ型の判定

$ dotnet script
> class Foo {}
> class Bar : Foo {}
> class Baz : Bar {}
> Foo a = new Foo();
> Foo b = new Bar();
> Foo c = new Baz();
> a is Foo
true
> a is Bar
false
> b is Foo
true
> b is Bar
true
> b is Baz
false
> c is Foo
true
> c is Bar
true
> c is Baz
true
> a as Foo
Submission#0.Foo { }
> a as Bar        // null
> b as Bar
Submission#1.Bar { }
> b as Baz        // null
> c as Baz
Submission#2.Baz { }
> typeof(Foo).Name
"Foo"
> b.GetType()
[Submission#1+Bar]
> b.GetType().Name
"Bar"
> c.GetType().Name
"Baz"

●演算子の多重定義

//
// ratio/Program.cs : 有理数
//
// Copyright (C) 2016-2022 Makoto Hiroi
//
using System;
using System.Numerics;

struct Ratio : IComparable {
  public BigInteger Numer { get; }  // 分子
  public BigInteger Denom { get; }  // 分母

  public Ratio(BigInteger p, BigInteger q) {
    BigInteger a = BigInteger.GreatestCommonDivisor(p, q);
    Numer = p / a;
    Denom = q / a;
    if (Denom < 0) {
      Numer = - Numer;
      Denom = - Denom;
    }
  }

  public static Ratio operator +(Ratio a, Ratio b) {
    return new Ratio(a.Numer * b.Denom + b.Numer * a.Denom, a.Denom * b.Denom);
  }

  public static Ratio operator -(Ratio a, Ratio b) {
    return new Ratio(a.Numer * b.Denom - b.Numer * a.Denom, a.Denom * b.Denom);
  }

  public static Ratio operator *(Ratio a, Ratio b) {
    return new Ratio(a.Numer * b.Numer, a.Denom * b.Denom);
  }

  public static Ratio operator /(Ratio a, Ratio b) {
    return new Ratio(a.Numer * b.Denom, a.Denom * b.Numer);
  }
  
  // IComparable 用のメソッド
  public int CompareTo(Object? obj) {
    if (!(obj is Ratio)) return 1;
    Ratio r = (Ratio)obj;
    BigInteger a = Numer * r.Denom;
    BigInteger b = r.Numer * Denom;
    if (a == b) return 0;
    else if (a < b) return -1;
    else return 1;
  }

  public override bool Equals(object? obj) {
    if (!(obj is Ratio)) return false;
    Ratio r = (Ratio)obj;
    return Numer == r.Numer && Denom == r.Denom;
  }

  // とても適当なので実際に使ってはいけない
  public override int GetHashCode() {
    return ((Numer << 7) ^ Denom).GetHashCode();
  }

  public static bool operator ==(Ratio a, Ratio b) {
    return a.Equals(b);
  }
  public static bool operator !=(Ratio a, Ratio b) {
    return !a.Equals(b);
  }
  public static bool operator <(Ratio a, Ratio b) {
    return a.CompareTo(b) < 0;
  }
  public static bool operator <=(Ratio a, Ratio b) {
    return a.CompareTo(b) <= 0;
  }
  public static bool operator >(Ratio a, Ratio b) {
    return a.CompareTo(b) > 0;
  }
  public static bool operator >=(Ratio a, Ratio b) {
    return a.CompareTo(b) >= 0;
  }

  // 表示
  public override string ToString() {
    return Numer.ToString() + "/" + Denom.ToString();
  }
}

class Test {
  static void Main() {
    Ratio a = new Ratio(1, 2);
    Ratio b = new Ratio(1, 3);
    Ratio c = new Ratio(1, 4);

    Console.WriteLine(a);
    Console.WriteLine(b);
    Console.WriteLine(c);
    Console.WriteLine(a + b);
    Console.WriteLine(a - b);
    Console.WriteLine(a * b);
    Console.WriteLine(a / b);
    Console.WriteLine(c + b);
    Console.WriteLine(c - b);
    Console.WriteLine(c * b);
    Console.WriteLine(c / b);
    Console.WriteLine(a == c + c);
    Console.WriteLine(a != c + c);
    Console.WriteLine(a < b);
    Console.WriteLine(a > b);
    Console.WriteLine(a <= b);
    Console.WriteLine(a >= b);
    Console.WriteLine(b < c);
    Console.WriteLine(b > c);
    Console.WriteLine(b <= c);
    Console.WriteLine(b >= c);

    var buff = new Ratio[] {b, a, c};
    Array.Sort(buff);
    foreach(Ratio x in buff) {
      Console.Write("{0} ", x);
    }
    Console.WriteLine("");
  }
}
$ dotnet run --project ratio
1/2
1/3
1/4
5/6
1/6
1/6
3/2
7/12
-1/12
1/12
3/4
True
False
False
True
False
True
False
True
False
True
1/4 1/3 1/2

●null 許容型

$ dotnet script
> int? a = 10;
> int? b = null;
> a
10
> b       // null
> int c = a ?? -1;
> c
10
> int d = b ?? -1;
> d
-1
> a + a
20
> a + b   // null
>

●日付と時刻

$ dotnet script
> var a = new DateTime(2022, 1, 1);
> var b = new DateTime(2022, 2, 1);
> a
[2022/01/01 0:00:00]
> b
[2022/02/01 0:00:00]
> var c = b - a;
> c
[31.00:00:00]
> c.TotalDays
31
> c.TotalHours
744
> c.TotalSeconds
2678400
リスト : 簡単な実行時間の計測 (sample19/Program.cs)

using System;

class Test {
  // 二重再帰
  static long Fibo(long n) {
    if (n < 2) return n;
    return Fibo(n - 2) + Fibo(n - 1);
  }

  static void Main() {
    var s = DateTime.Now;
    Console.WriteLine(Fibo(42));
    Console.WriteLine((DateTime.Now - s).TotalSeconds);
  }
}
$ dotnet run --project sample19
267914296
4.0949824
$ dotnet run -c Release --project sample19
267914296
2.6287564

実行環境 : Ubunts 18.04 (WSL), Intel Core i5-6200U 2.30GHz

●書式指定文字列

$ dotnet script
> 255.ToString("d")
"255"
> 255.ToString("x")
"ff"
> 255.ToString("X")
"FF"
> double x = 1.23456789;
> x.ToString("f")
"1.235"
> x.ToString("e")
"1.234568e+000"
> x.ToString("g")
"1.23456789"
> 123456789.ToString("n")
"123,456,789.000"
> var a = new DateTime(2022, 2, 1, 12, 34, 56);
> a
[2022/02/01 12:34:56]
> a.ToString("d")
"2022/02/01"
> a.ToString("D")
"2022年2月1日火曜日"
> a.ToString("t")
"12:34"
> a.ToString("T")
"12:34:56"
> a.ToString("f")
"2022年2月1日火曜日 12:34"
> a.ToString("F")
"2022年2月1日火曜日 12:34:56"
> a.ToString("g")
"2022/02/01 12:34"
> a.ToString("G")
"2022/02/01 12:34:56"
> Console.WriteLine("[{0,8}]", "hello")
[   hello]
> Console.WriteLine("[{0,-8}]", "hello")
[hello   ]
> Console.WriteLine("[{0,-8:d}]", 123)
[123     ]
> Console.WriteLine("[{0,8:d}]", 1234)
[    1234]
> Console.WriteLine("[{0,-8:d}]", 1234)
[1234    ]
> Console.WriteLine("[{0:d8}]", 1234)
[00001234]
> Console.WriteLine("{0:f}", 1.2345678)
1.23
> Console.WriteLine("{0:f7}", 1.2345678)
1.2345678
> Console.WriteLine("{0:###.###}", 123.45)
123.45
> Console.WriteLine("{0:###.000}", 123.45)
123.450
> Console.WriteLine("{0:0000.000}", 123.45)
0123.450

初版 2016 年 9 月 - 11 月
改訂 2022 年 2 月 12 日

Copyright (C) 2016-2022 Makoto Hiroi
All rights reserved.

[ Home | C# ]