[csharp: C# note] Object-oriented, and type-safe programming language by Microsoft. #csharp
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 のドキュメント - 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 が利用される ... このへんややこしい。
C# のバージョンは利用する .NET 環境バージョンに依存するっぽい。
$ dotnet --version
で .NET バージョン確認可能macOS なら xcode, dotnet, mono あたりを入れておけばコンソールでサクッと動かせる。
$ csharp
Mono C# Shell, type "help;" for help
Enter statements below.
csharp>
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",
},
↑ コーディング規約ざっと読むと何があるかイメージ付きやすい。静的型付け、Java や最近のモダンな言語にある機能は殆どありそう。
ローカル変数、private プロパティ、引数の 3 点以外、多くの命名で PascalCase が利用されているっぽいので注意。ざっくり「ローカルスコープの変数」は全部 camelCase で、それ以外全部 PascalCase 。
↑ 古い記事で、最新 Java との比較ではないが どうせ Java なんて古い環境でしか触ることないし C# のもつ特徴がよくわかる。
string[0]
のようにコレクションにアクセス可能virtual
を、オーバライドしたメソッドに override
をつけて禁則を解除する==
で比較可能、Ruby のように演算子オーバーロードもできるJava っぽい点として、1 つのエントリポイントクラスの Main()
が呼び出されてプログラムが実行される点は同じ。まぁ FW 使ってるときに気にすることは殆どないが。
class TestClass
{
static void Main(string[] args)
{
// Display the number of command line arguments.
Console.WriteLine(args.Length);
}
}
LL 出身者なのでこのあたりいつも「え?」ってなる。
function
キーワードがなく「関数型」とかない
users.find(u => u.id === 1);
みたいなねfunction
オブジェクトはないが、後述する「 delegate 」を利用した Action, Func などのクラスと、匿名メソッド式/ラムダ式による「匿名関数」でこれを解決している
System.Func<..>
が値を返すやつSystem.Action
が値を返さないやつ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
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 class MyClass { int a; }
partial class MyClass { int b; }
// ↑ と同義
class MyClass { int a; int b; }
文字列 string, 配列 array, コレクションの List や Dictionary あたりは他言語と勝手が違うので注意。全部プリミティブなデータ型ではなく、クラス実装されており、参照型 (代入時に参照が渡される) ので注意。感覚的には JS の String.prototype や Array.prototype あたりが近いか。
char
のコレクションらしいstring
のように型指定可能で、System.String クラスのエイリアスになってる""
で囲う必要あり ( ''
だと char 扱い )int[]
のように型指定可能[]
アクセスできるようになってる、素敵いわゆる 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;
}
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 型宣言をするの くそかったるくて 大変なので、汎用的なメソッドのデリゲート型として Action や Func が提供されている。
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);
メソッド内に、そのメソッド内でのみ有効なローカル関数が定義できるらしいが ... 使い所あるのかな。公式でも「まぁぶっちゃけラムダで同じことできます」言うとる。
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 + @"\";
}
}
object obj1 = null;
object obj2 = new object();
object obj3 = new object();
// || だと falsy かどうかになってしまうが
// ?? なら null かどうか?を厳密に見れる
//
return obj1 ?? obj2 ?? obj3; // obj2
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