C# で順列の全パターンを列挙する
順列とは
nPm で表現される、n 個の要素から m 個を選択して得られる重複のない有限列のこと。
例えば ₃P₂ の場合は以下のように列挙される。
1, 2 1, 3 2, 1 2, 3 3, 1 3, 2
C# ではこれを実現するためのライブラリがデフォルトでは用意されていないため、自前で実装する必要があります。
コード
C# には yield return
や LINQ があるため比較的簡潔に実装することができます。
以下、実装
using System.Collections.Generic; using System.Linq; namespace UniDotLog { public class Sample { public IEnumerable<int[]> GetPermutation(int n, int m) => GetPermutationInternal(Array.Empty<int>(), Enumerable.Range(1, n), m); private IEnumerable<int[]> GetPermutationInternal(IEnumerable<int> selected, IEnumerable<int> candidates, int rest) { if (rest == 0) { yield return selected.ToArray(); yield break; } foreach (var item in candidates) foreach (var result in GetPermutationInternal(selected.Append(item), candidates.Where(x => x != item), rest - 1)) yield return result; } } }
もしくはクエリ式を使って
using System.Collections.Generic; using System.Linq; namespace UniDotLog { public class Sample { public IEnumerable<int[]> GetPermutation(int n, int m) => GetPermutationInternal(Array.Empty<int>(), Enumerable.Range(1, n), m); private IEnumerable<int[]> GetPermutationInternal(IEnumerable<int> selected, IEnumerable<int> candidates, int rest) { if (rest == 0) return new [] { selected.ToArray() }; return from item in candidates from result in GetPermutationInternal(selected.Append(item), candidates.Where(x => x != item), rest - 1) select result; } } }
foreach
がネストするようなケースの場合、クエリ式の方が綺麗に書けることがあります。
return new [] { selected.ToArray() };
の箇所は以下のような拡張メソッドを噛ませて return selected.ToArray().Singleton();
としても良いかと思います。
public static class Ex { // Singleton はデザインパターン的な意味での Singleton ではなく数学的な意味での Singleton public static IEnumerable<T> Singleton<T>(this T x) => new [] { x }: }
C# - プロパティについて
プロパティ
プロパティとは
オブジェクト指向でよく使われる概念であるところのカプセル化の実装を補助するための機能です。
プロパティを使わない実装
プロパティを使わない場合は以下のように定型的な記述が非常に多いコードを書く必要があります。
private string capsuledValue; public string GetCapsuledValue() { return capsuledValue; } private string SetCapsuledValue(string value) { capsuledValue = value; }
C# 1.x
プロパティは C# の初期から使えていました。が、初期段階ではアクセスレベルがプロパティ単位でしか指定できなかったため、setter は関数かするなりフィールドを直接触るなりしないとダメで setter が存在している意味がかなり薄く、利便性もそれほど高くありませんでした。
先程のコードを置き換えてみます。
private string capsuledValue; public string CapsuledValue { get { return capsuledValue; } } private string SetCapsuledValue(string value) { capsuledValue = value; }
あまり変わりませんね。
ちなみに getter と setter はただの関数なので、関数でできることはプロパティでも大抵可能です。
private string sample; public string Sample { get { // 値を返す前にしたい処理色々 return sample; } set { // 値を入れる前にしたい処理色々 sample = value; // 値を入れた後にしたい処理色々 } }
C# 2.0
これが 2.0 になると getter と setter それぞれに異なるアクセスレベルを指定できるようになったため、有用性が増し、非常に頻繁に利用されるようになりました。
private string capsuledValue; public CapsuledValue { get { return capsuledValue; } private set { capsuledValue = value; } }
このように getter と setter の関数を自前で実装する必要がなくなりかなりシンプルになりました。
3.0
C# 3.0 ではこれがさらに省略できるようになり、変数への値の出し入れをするだけのプロパティであれば フィールドを getter と setter の中身を記述しなくても良くなりました。
public CapsuledValue { get; private set; }
非常にシンプルで大変使い勝手が良くなりました。
Model オブジェクトだと値の出し入れだけできれば良いケースが大半なため、この記法は非常に多用されます。
6
get-only プロパティ
C# 6 ではさらに省略することが可能となり、コンストラクタからの set しか受け付けない、 get-only プロパティを定義することができるようになりました。
public class Sample { public string CapsuledValue { get; } public Sample(string value) { CapsuledValue = value; } }
これによってさらにシンプルな記述ができるようになった上にイミュータブル%20%E3%81%AA,%E3%82%92%E5%A4%89%E3%81%88%E3%82%8B%E3%81%93%E3%81%A8%E3%81%8C%E3%81%A7%E3%81%8D%E3%82%8B%E3%80%82)なプロパティを作成することができるようになりました。
コンストラクタでしか設定できない、といえば readonly です。
そして get-only プロパティは以下のような実装に展開されています。
public class Sample { private readonly string capsuledValue; public string CapsuledValue { get { return capsuledValue; } } public Sample(string value) { capsuledValue = value; } }
実際に readonly なフィールドが作られています。
で、これ私の理解が足りないんだと思いますが、public readonly
なフィールドを定義した場合との使い所の違いが今ひとつわかっていません。
public に公開されるところは全てプロパティにしてしまえって考え方で、その一貫性を保つため、という事であればまぁわかるんですけど。*1
他に何か理由があればコメントなりなんなりで教えていただけると嬉しいです。
プロパティ初期化子
プロパティを通常のフィールドのように初期値を設定できるようになりました。
これまでは以下のように対応していました。
public class Sample { private string value1 = "value1"; public string Value1 { get { return value1; } set { value1 = value; } } public string Value2 { get; private set; } public Sample() { Value2 = "value2"; } }
プロパティ初期化子が使えるようになると以下のように記述できます。
public class Sample { public string Value1 { get; private set; } = "value1"; public string Value2 { get; private set; } = "value2"; }
expression-bodied なプロパティ
日本語では式形式のメンバーと呼ばれていますが、個人的には expression-bodied の方が馴染みがあるのでこちらで。*2
C# 6 ではget のみの場合に限り、ラムダ式でプロパティを定期できるようになりました。
具体的には以下のような記述になります。
private string sample; public string Sample => sample.Replace("hoge", "sample");
get と記述する必要がなくなり、よりシンプルに書けるようになりました。
ezpression-bodied はプロパティに限らず、関数にも利用できるので文脈に応じてケースバイケースで使い分けるようにしましょう。
プロパティは比較的単純に get する処理系、関数は何がしか意味を持って処理をする処理系、の場合、と使い分けると良いと思います。
7
C# 7 で expression-bodied なプロパティが拡張され、setter でも利用可能になりました。できることは変わらず、ラムダ式でワンライナーで書ける範囲のことを記述できます。
private string sample; public Sample { get => sample: set => sample = value.Replace("hoge", "sample"); }
C# - 名前空間について
一気に複数書こうと思ったけどボリューム的に厳しいので名前空間だけ。
名前空間
えっ、これ 1.x 系では無かったんだ!?ってなるくらい今では当たり前の言語機能。
これは 2.0 から使えるようになりました。
何ができるのか
名前空間を使うことで、クラスや構造体を階層化して整理することができます。
階層構造で一番よく目にするのはフォルダ構造かと思います。
技術書 ├ インフラ │ ├ Windows │ │ ├ … │ ├ Linux │ … ├ プログラミング │ ├ サーバー │ ├ クライアント … …
こんなイメージ。適切にカテゴライズすることで情報への到達を容易にできるようにする手段です。
namespace UniNamespace { class Uni { } }
の形式で記述することで利用できます。
名前空間を定義することで、クラスや構造体の整理ができるのはもちろんそうなのですが、
他にも他人が作ったものとの重複を避けられることも大きなメリットになります。
名前空間を分けずに定義した場合、他人が作ったクラスを利用したいとなった時に面倒なことになったりします。
初期のUnityなんかはそもそも名前空間のサポートをしなかったりしていたので割と困った子だったようです。*1
使い方
そんな名前空間ですが、以下のように二種類の階層化方法があります。
namespace ParentNamespace { namespace ChildNamespace { public class Hoge { } } }
namespace ParentNamespace.ChildNamespace { public class Hoge { } }
が、可読性の問題もあるので基本的に後者しか見ないと思います。
少なくとも私はこれまで前者の記述方法を実プロダクトで見たことはありません。
名前空間に定義されたクラスの呼び出し方はいくつかあります。
一つ目は名前空間を using
で宣言して使う方法。
using ParentNamespace.ChildNamespace; namespace TekitouNamespace { public class Foo { Hoge hoge = new Hoge(); } }
二つ目は名前空間を含めたフルネーム(完全就職名
)で呼び出す方法。
namespace TekitouNamespace { public class Foo { ParentNamespace.ChildNamespace.Hoge hoge = new ParentNamespace.ChildNamespace.Hoge(); } }
三つ目は同じ名前空間に属した状態で処理を記述する方法。
namespace ParentNamespace.ChildNamespace { public class Foo { Hoge hoge = new Hoge(); } }
四つ目、これはちょっと番外編になりますが、エイリアスを使ってクラスに別名をつける方法。
using Hage = ParentNamespace.ChildNamespace.Hoge; namespace TekitouNamespace { public class Foo { Hage hage = new Hage(); } }
四つ目の方法は別の名前空間内に同一名称のクラスが存在している場合や長大な名前のクラスやインターフェイスを省略したりとかいう場合に使われます。
C# 2.0 - ジェネリックについて
C# の古い仕様から順に雑にまとめていく予定。
ジェネリック Generics
ジェネリックとは
ジェネリックとは、クラスや構造体、関数に対して型のパラメータを持たせることができるようになる機能のことです。
実際には <T>
や <T, U>
<TKey, TValue>
などの形式で見かけます。((T は Type
つまり型の省略形で非常によく使用されます))*1
C# のコードでよく見るものとしては Dictionary<TKey, TValue>
, List<T>
, Task<T>
, Enumerable.Select<TSource, TResult>
など。
T などの事を型引数
と言います。
*1:U は単にアルファベット順で T の次という理由でこれもよく使用されます
Docker Desktop Developer Preview Program に参加してみる
C# 8 から導入された非同期ストリーム機能を使う
C# 8 以前では以下のような形で async/await
を用いたコード上で yield return
をすることはできなかった。
public async Task<IEnumerable<int>> GetHogesAsync() { var items = await GetItemsAsync(); foreach (var item in items) { yield return item + 10; // <- ここでコンパイルエラー } }
これが、C# 8 で上記とは異なる形で、Task<IEnumerable<T>>
の代わりに IAsyncEnumerable<T>
を使うことで実現可能となった。
IAsyncEnumerable<T>
は foreach を await することで非同期的に列挙することができる。
具体的には以下の構文となる。
await foreach (var item in items) { }
現時点ではLINQ的な拡張が用意されていないため、もし LINQ のようなことがしたい場合は自前で用意する必要がある。*1
実際にはこんな感じなコードを書けばとりあえずLINQっぽく書くことができるようになる。
が、これだけだと例えば Task<IEnumerable<T>>
な型だったり Task<T[]>
な型だったりに対応できないので AsAsyncEnumerable<T>
な拡張を用意してあげても良いかもしれない。
この場合、Task<IEnumerable<T>>
だけだと C# の型推論の関係上 Task<T[]>
などを網羅できないので、個別に記述していく必要がある。
*1:NuGet が何かあるかもしれないけど未調査
M1 MacBook Air で Visual Studio Code を使う
Visual Studio Code を入れる
Experimental ではあるけどすでに ARM64 対応なものが出てきているのでそれを使う。
コマンドからも使いたいので VSCode 上から使えるようにする。
Command + Shift + P
を開いて shell と入力して最初に出てくる候補を実行。
Successfully
これで code-exploration
で実行できるように。
code-exploration だとタイプするのが面倒なので通常通り単に code で実行できるようにする。
% which code-exploration /usr/local/bin/code-exploration % cd /usr/local/bin % sudo cp -R code-exploration code
とりあえずざっくり動かした感じは問題なさそう。
では .NET 5 を使ってちょっとした開発をしてみましょう。
と、思ってサクッと dotnet new console
をして F5
実行をしてみたところ以下のエラー。
[WARNING]: Processor architecture 'arm64' is not currently supported by the .NET Core debugger. Debugging will not be available.
なるほどなるほどそりゃそうだ。
というわけで、現時点で .NET 5 を使った開発自体はできるものの、デバッガが ARM に対応していないため、ステップ実行やデバッグができず、開発のメイン機として使うのは相当な覚悟が必要そうです。
printf デバッグで頑張れるならアリ、なのかな。きっと。
追記: Visual Studio Code の x64 版を使えば一応動くっぽい気配。あとで試してみる。