yano3nora
7/2/2017 - 1:34 PM

[c#: note] c# note. #cs

[csharp: C# note] Object-oriented, and type-safe programming language by Microsoft. #csharp

Overview

C# リファレンス - docs.microsoft.com
C Sharp - wikipedia.org

Microsoft 製のクラスベース・オブジェクト指向なプログラミング言語。C 系言語 (特に Java) や Delphi の機能を多数参考に色々取り入れた「俺の考えた最強の Java 」。拡張子は .cs で C#, C Sharp などと呼ばれる。基本 Java っぽいが、比較すると割と先進的で、新しい機能バンバンいれてるみたい。

c# とかだと検索ひっかからなかったり、Web サービスのハッシュタグとの相性が悪いせいか csharp 呼称が多い印象。

X Platform 開発に用いられる Xamarin や、ゲーム開発の Unity などで利用されることが多い。

名前が C, C++ と似ているためよく比較されているが全くの別物。C, C++ はメモリ制御をプログラマにやらせるハード制御が得意なコンパイル言語で、OS や IoT 製品のプログラムに利用される。対して C# は Java 同様に中間コードを用いたコンパイル言語で GC があり、ソフトウェア全般で利用される。

単純なシンタックスや言語機能の比較は C#とC++の比較 が参考になる。

.NET

.NET のドキュメント - docs.microsoft.com
参考: .NET Core / .NET Framework / Xamarin / Monoの関係を整理する

C# は Java のような「中間コード」を利用したコンパイル形式をとっている。Windows では .NET Framework 実行環境において、共通中間言語 (中間コード) にコンパイルされ、同実行環境上でネイティブコードに変換 → 実行される。

ちなみに最近は .NET Framework は従来の「.NET 環境の Windows 専用実装」を指し、OS によらない .NET 実行環境の総称を .NET と呼ぶように区別されてるよう。Linux や macOS では .NET Framework の代替として Mono が利用される ... このへんややこしい。

Versions

C# 言語のバージョン管理 - docs.microsoft.com

C# のバージョンは利用する .NET 環境バージョンに依存するっぽい。

  • 最新は 9 系
    • .NET 5.0 ~
    • Unity 対応はまだっぽい
  • 新しめで普及し始めてるのが 8 系
  • 最近メジャーなのは 7 系
    • .NET Core 2.x
    • Unity 2018 ~
  • $ dotnet --version で .NET バージョン確認可能

Interactive Shell

macOS なら xcode, dotnet, mono あたりを入れておけばコンソールでサクッと動かせる。

$ csharp

Mono C# Shell, type "help;" for help

Enter statements below.
csharp>  

scriptcs

mono を内包してるのか brew install 時に一緒に入る (先に mono を install してたらそいつ使う) のか調べてない。サクッと csharp script 書いて vscode の code runner とかで実行したいとき用のやつ。

$ brew install scriptcs
using System;

// top-level に命令文書く時は
// 定義文の前に書けって linter さんに怒られる
//
Program.Main();

// scriptcs のときは依存してるやつから書く
//
public class MyClass
{
    public string hello = "Hello World";
}

// using 依存のもの (e.g. List とか) は
// top-level scope から参照できない?ので
// (ちょっと csharp の仕様わかってない)
// 実行用の main メソッドを用意してやる
//
public class Program
{
    public static void Main()
    {
        MyClass obj = new MyClass();
        Console.WriteLine(obj.hello);  // Hello World
    }
}
// vscode の code-runner 設定で指定とか
"code-runner.executorMap": {
  "csharp": "scriptcs -script",
},

Syntax

C# のコーディング規則 (C# プログラミング ガイド)
C# CODING GUIDELINES

↑ コーディング規約ざっと読むと何があるかイメージ付きやすい。静的型付け、Java や最近のモダンな言語にある機能は殆どありそう。

ローカル変数、private プロパティ、引数の 3 点以外、多くの命名で PascalCase が利用されているっぽいので注意。ざっくり「ローカルスコープの変数」は全部 camelCase で、それ以外全部 PascalCase 。

Java との比較

なぜJavaはC#と比べて駄目なのか

↑ 古い記事で、最新 Java との比較ではないが どうせ Java なんて古い環境でしか触ることないし C# のもつ特徴がよくわかる。

  • int, string などプリミティブなデータ型が Ruby のようにオブジェクト (厳密には struct = 構造体) として実装されていて、拡張メソッドでメタプロできる
  • Java 的な setter / getter は定義せず、素直にクラス定義でプロパティをセットできる
  • Ruby のようにオブジェクト指向ながらも string[0] のようにコレクションにアクセス可能
  • メソッドのオーバライドは原則禁止で、オーバライドする可能性のあるメソッドに virtual を、オーバライドしたメソッドに override をつけて禁則を解除する
  • 1 ファイル 1 public クラスの制限はない
  • 文字列は == で比較可能、Ruby のように演算子オーバーロードもできる

エントリポイント / Main()

Main() とコマンドライン引数 (C# プログラミング ガイド)

Java っぽい点として、1 つのエントリポイントクラスの Main() が呼び出されてプログラムが実行される点は同じ。まぁ FW 使ってるときに気にすることは殆どないが。

class TestClass
{
    static void Main(string[] args)
    {
        // Display the number of command line arguments.
        Console.WriteLine(args.Length);
    }
}

function (関数) が第一級オブジェクトでない

Is there a Function type in C#?

LL 出身者なのでこのあたりいつも「え?」ってなる。

  • そもそも全部クラスなので「メソッド」しかない
  • いわゆる function キーワードがなく「関数型」とかない
    • 変数に関数を代入したりできない
    • 関数型ちっくにかけない
    • 具体的には関数を変数に代入 → メソッドの引数にコールバックとしてその変数を指定とか、無名関数を指定とかできない
    • JS でよくやる users.find(u => u.id === 1); みたいなね
  • Java 8 では 関数型インタフェース でこれを解決してる
  • C# では function オブジェクトはないが、後述する「 delegate 」を利用した Action, Func などのクラスと、匿名メソッド式/ラムダ式による「匿名関数」でこれを解決している
    • System.Func<..> が値を返すやつ
    • System.Action が値を返さないやつ

クラス

クラス (C# プログラミング ガイド)

using System;

public class Person
{
    // Constructor that takes no arguments:
    public Person()
    {
        Name = "unknown";
    }

    // Constructor that takes one argument:
    public Person(string name)
    {
        Name = name;
    }

    // Auto-implemented readonly property:
    public string Name { get; }

    // Method that overrides the base class (System.Object) implementation.
    public override string ToString()
    {
        return Name;
    }
}

class TestPerson
{
    static void Main()
    {
        // Call the constructor that has no parameters.
        var person1 = new Person();
        Console.WriteLine(person1.Name);

        // Call the constructor that has one parameter.
        var person2 = new Person("Sarah Jones");
        Console.WriteLine(person2.Name);
        // Get the string representation of the person2 instance.
        Console.WriteLine(person2);

        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
// Output:
// unknown
// Sarah Jones
// Sarah Jones

静的クラス

  • よくあるインスタンス化できないクラス
  • staic メンバしか持てなくなる
public static class StringUtil
{
  public static string Repeat(this string str, int count)
  {
    var array = new string[count];
    for (var i = 0; i < count; ++i) array[i] = str;
    return string.Concat(array);
  }
}

匿名クラス

// まぁ、こういうこともできますよ。みたいな、使うかな ... ?
var fruitList = new[] {
    new { Name = "りんご", Price = 300 },
    new { Name = "バナナ", Price = 200 },
    new { Name = "パイナップル", Price = 1000 },
    new { Name = "いちご", Price = 500 },
};

部分型 partial

クラスやオブジェクトやメソッドを部分型 partial で分割定義できる。

partial class MyClass { int a; }
partial class MyClass { int b; }

// ↑ と同義
class MyClass { int a; int b; }

データ型

組み込み型

文字列 string, 配列 array, コレクションの List や Dictionary あたりは他言語と勝手が違うので注意。全部プリミティブなデータ型ではなく、クラス実装されており、参照型 (代入時に参照が渡される) ので注意。感覚的には JS の String.prototype や Array.prototype あたりが近いか。

String Interpolation

いわゆる template literal はこう。$"もじれちゅ{var}"

string name = "Mark";
var date = DateTime.Now;

Console.WriteLine($"Hello, {name}! Today is {date.DayOfWeek}, it's {date:HH:mm} now.");

値型

代入時にコピるやつ。

sbyte
byte
short
ushort
int number = 123;
uint
long
ulong

float
double score = 12.5;
decimal

bool  isChecked = true;
bool? flag      = null;  // null 許容型

char initial = 'A';  // U+0000 ~ U+FFFF の表現であり、数値変換可能なやつ

// 列挙型
enum Season
{
    Spring,
    Summer,
    Autumn,
    Winter
}

// タプル型
// Python など一般的なタプルと違い変更可能なことに注意
// https://www.buildinsider.net/column/iwanaga-nobuyuki/016
(double, int) t1 = (4.5, 3);

// 構造体型 https://takap-tech.com/entry/2018/10/19/004932
// データがメモリスタック領域に割り当てられる
// (OSやコンパイラが割り当てる領域で、プログラムから動的に動かせないやつ)
// 引数が「値渡し」になる、ユーザコードで使うことは殆どない
public struct Coords
{
    public Coords(double x, double y)
    {
        X = x;
        Y = y;
    }

    public double X { get; }
    public double Y { get; }
    public override string ToString() => $"({X}, {Y})";
}

参照型

代入時に参照わたるやつ。

string message = "hello" + "world!";

class Klass {}
interface MyInterface {}

// デリゲート
// メソッドをカプセル化することができる参照型
// C++ の関数ポインターに似ているがタイプセーフ
// ... 何言ってるかわからないので後でまとめる
public delegate void SayHello(string name);

object Home {}  // 基底クラス System.Object の別名

// Null 許容参照型
string? nullable = null;

// 配列はクラス扱い

その他

void     // 値なし
var      // 型推論
dynamic  // object っぽくふるまう
         // コンパイル時に型推論し独自型を生成 (あってないかも)

ジェネリクス / 型引数

Java, TypeScript 同様にジェネリクスサポートで「型引数」をクラス・メソッド・インターフェイスに渡せる。

int Max(int x, int  y)
{
  return x > y ? x : y;
}

// ↑ と同じことを double 型でやりたいとき
// 同じような関数が必要になる
double Max(double x, double y)
{
  return x > y ? x : y;
}

// <> で型引数を使えば 1 メソッドで済む
// TypeScript などでは慣習的に <T> とか <T, U, P> とか使われるが
// C# だとどうなんだろ
public static Type Max<Type>(Type a, Type b)
  where Type : IComparable
{
  return a.CompareTo(b) > 0 ? a : b;
}

delegate / ラムダ式

デリゲートの概要
ラムダ式 (C# リファレンス)

C# は「処理のカプセル化 (変数に入れて使い回すとか)」に delegate という仕組みを利用している。Ruby の Delegate や Forwardable などと意味するところは同じで クラスのメソッドコール時に別クラスのメソッドに処理内容を委譲する みたいなことのはず。

# https://docs.ruby-lang.org/ja/latest/method/Forwardable/i/def_delegator.html

require 'forwardable'

class MyQueue
  extend Forwardable
  attr_reader :queue

  def initialize
    @queue = []
  end

  # MyQueue.mypush されたら @queue.push に処理委譲してね
  def_delegator :@queue, :push, :mypush
end

q = MyQueue.new
q.mypush 42
q.queue  # => [42]

当初 C# は Java 同様ガチガチのクラスベース OOP で function オブジェクトがなかったため、クラス定義内の 匿名メソッド - Anonymous Method で ↑ のように「メソッドを委譲する」ことでしか「処理のカプセル化 → 他クラスにお渡し」が出来なかったのだと思う。

// 返り値のない MyAction メソッドをデリゲート型として宣言
// 要は「実際の処理内容を、この型のインスタンスに割り当てられた匿名メソッドに委譲する」型
//
delegate void MyAction(string message);

class Program
{
    static void Main(string[] args)
    {
        // ここで MyAction 型の action に委譲された実際の処理内容を
        // 匿名メソッド構文 delegate {} で定義している
        // 他言語の第一級オブジェクト function action = ... に相当
        //
        MyAction action = delegate (string message)
        {
            Console.WriteLine(message); // 匿名メソッドの内容
        };

        // メソッド MyAction デリゲートクラス型のインスタンス action ちゃんは実処理を
        // Program クラス内の delegate {} 内匿名メソッドに委譲している
        //
        action("Hello! World!"); // Hello! World!
    }
}

↑ について匿名メソッドを使う度に delegate 型宣言をするの くそかったるくて 大変なので、汎用的なメソッドのデリゲート型として ActionFunc が提供されている。

class Program
{
    static void Main(string[] args)
    {
        // 独自型定義を省略したので、型定義で行っていた引数の型解決を
        // ジェネリクスで解決 (ライブラリ側で 16 個までジェネリクス受け付けてるぽい)
        //
        Action<string> action = delegate (string message)
        {
            Console.WriteLine(message);
        };

        action("Hello! World!"); // Hello! World!
      
        // 返り値があるやつは Func を使う、返り値はジェネリクスのケツですね
        //
        Func<string, string> action2 = delegate (string name) {
            Console.WriteLine("Hello! " + name);
        };

        action2("John!");  // Hello! John!
    }
}

で、今度は「 delegate () {} 書くの面倒くね?そもそもメソッドの委譲とかややこしくね? Ruby の Proc みたいに関数として扱えねえの?」という クレーム 要望から ラムダ式による匿名関数 が追加され、より完結に委譲が可能になった ... んだと思う。

/**
 * ラムダ式自体は「匿名関数」の定義
 * しかし ↑ この匿名関数は「デリゲート型」に変換可能
 * ので、従来の Action, Func 型のインスタンスに割当ることができ
 * 結果 `delegate (){}` 書かなくて済むようになった → やったね!! 
 *
 * 書き方とか省略記法なんかは JS と似てる
 */

// 返り値のない Action 型メソッド
Action myAction = () => {
    Console.WriteLine("Hello");
};

// 返り値のある Func 型メソッド
Func<bool> myFunc = () => {
    return true;
};

// これでようやく、変数に格納した関数をコールバックとして
// 何らかのメソッドの引数に渡したりできるようになった ... お疲れ様です
//
Klass.SomeFunc(arg, myAction);

// System.Linq の Enumerable.Where メソッド
// https://docs.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.where?view=net-5.0
//
// コレクションを走査 → コールバックで true なやつだけにフィルタリングするやつ
// 第一引数はデフォルトで this (この場合データソース someValues)
// 第二引数が Func<TSource, Boolean> をとる ... ということで、やっとここに
// delegate {} or () => {} が入ってよいということがわかる ... おやすみなさい
//
someValues.Where(x => x == 2);

ローカル関数

ローカル関数 (C# プログラミング ガイド)

メソッド内に、そのメソッド内でのみ有効なローカル関数が定義できるらしいが ... 使い所あるのかな。公式でも「まぁぶっちゃけラムダで同じことできます」言うとる。

private static string GetText(string path, string filename)
{
     var reader = File.OpenText($"{AppendPathSeparator(path)}{filename}");
     var text   = reader.ReadToEnd();

     return text;

     string AppendPathSeparator(string filepath)
     {
        return filepath.EndsWith(@"\") ? filepath : filepath + @"\";
     }
}

null 合体演算子

object obj1 = null;
object obj2 = new object();
object obj3 = new object();

// || だと falsy かどうかになってしまうが
// ?? なら null かどうか?を厳密に見れる
//
return obj1 ?? obj2 ?? obj3;  // obj2

yield / コルーチン

yield (C# リファレンス)

Iterable なオブジェクトを返すメソッド内などで利用して、処理をコルーチン化 (反復実行・途中から再実行可能にする) する例のやつ。

static IEnumerable<int> Generate()
{
    for (int i = 0; i < 10; i++)
    {
        yield return i; 
        Thread.Sleep(TimeSpan.FromSeconds(1));
    }
}

static void Display()
{
    foreach (int i in Generate())
    {
        Console.WriteLine($"Number: {i}");
    }
}

拡張メソッド

拡張メソッド (C# プログラミング ガイド)
拡張メソッド - 古いけど、多分ここが一番ちゃんと書いてる気がする

built-in な class (↓ では System.String クラス) に独自メソッドを追加できる。LINQ とかはこれで基底クラスに色々生やしてるらしい。

using System.Linq;
using System.Text;
using System;

namespace CustomExtensions
{
    public static class StringExtension
    {
        // ここでメソッドの引数の前に this (StringExtension) を置くことで
        // using で import したとき string 型の .WordCount にアクセスが有った際に
        // この static メソッドが評価されるようにしている
        //
        public static int WordCount(this string str)
        {
            return str.Split(new char[] {' ', '.','?'}, StringSplitOptions.RemoveEmptyEntries).Length;
        }
    }
}
namespace Extension_Methods_Simple
{
    using CustomExtensions;

    class Program
    {
        static void Main(string[] args)
        {
            string s = "The quick brown fox jumped over the lazy dog.";
            int    i = s.WordCount();

            System.Console.WriteLine("Word count of s is {0}", i);
        }
    }
}

プリプロセッサ

プリプロセッサディレクティブ

コンパイル時に評価させるディレクティブを仕込める。

// DEBUG シンボルが定義されているときだけコンパイルされる
#if DEBUG
    Console.WriteLine("Debug version");
#endif

// {} などスコープによらないコードブロックを定義したりできる
// エディタで folding するときにつかうみたい
#region
#endregion