- 投稿日:2020-07-13T21:29:38+09:00
Java技術者のためのC#チートシート
背景
これまでJavaをメインに仕事してきたのですが、ほとんど触れてこなかったC#を仕事で使うことになったので勉強しました。
似ているところもありますが、思想の違いや微妙な書き方の違いがあるので「これどう書くんだっけ?」が絶対に起きると思いましたので、よく使いそうなところをピックアップしてチートシートとしてまとめてみました。
細かいところは追々知っていこうと思います。使用するパッケージの宣言
Java
呼び出し方が違う。
import java.util.ArrayList;C#
using System.Collections.Generic;パッケージの定義
宣言が違う。
また思想も違うのでネーミングは注意。Java
package StudyPackage.com.anyplusC#
namespace StudyPackage // 思想が違うためドメインがないアクセス修飾子
Javaにはない
internal
やprotected internal
が存在する。
同じ修飾子でも思想の違いによることでアクセス可能範囲が異なる事にも注意。
修飾子 Java C# public どこからでもアクセス可能 どこからでもアクセス可能 protected 同一のパッケージ、または派生したクラスからアクセス可能 派生したクラスからアクセス可能 internal 存在しない 同一のアセンブリ(DLL)内でアクセス可能 protected internal 存在しない 同一のアセンブリ(DLL)内、または格納しているクラスから派生した型からのみアクセス可能 private 同じクラス内からのみアクセス可能 同じクラス内からのみアクセス可能 指定なし
(default)同一のパッケージ内でアクセスが可能 privateと同じ扱い 同一のアセンブリ(DLL)とは
同一アセンブリとは、同一のexeファイルや、DLLファイルの事。
VisualStudioのソリューション内であれば、同一プロジェクトということらしい。継承
継承するときは
:
(コロン)を使用する。
オーバーライドさせたいメソッドにはvirtual
を付け、オーバーライドする時はoverride
をつける。
virtual
とoverride
を付けないと、インスタンスが生成されて利用可能状態となる。
オーバーライドしたくない時はnew
をつける。Java
class Base { public void printStr1(){ System.out.println("Baseの1です"); } public void printStr2(){ System.out.println("Baseの2です"); } } class SubA extends Base { public final void printStr1(){ System.out.println("SubAのprintStr1です"); } public void printStr2(){ System.out.println("SubAのprintStr2です"); } }C#
class Base { public void printStr1(){ Console.WriteLine("Baseの1です"); } public virtual void printStr2(){ Console.WriteLine("Baseの2です"); } } class SubA : Base { public new void printStr1(){ Console.WriteLine("SubAのprintStr1です"); } public override void printStr2(){ Console.WriteLine("SubAのprintStr2です"); } }if文
違いなし。
Stringの比較は等値演算子でも値比較可能という違いがある。Java
String strVal = ""; if("str".equals(strVal)){ System.out.println("同じだよ"); } else { System.out.println("違うよ"); }C#
string strVal = ""; if("str" == strVal){ // 補足 Console.WriteLine("同じだよ"); } else { Console.WriteLine("違うよ"); }補足
String型を参照型なのでJavaでは等値演算子で比較すると同じインスタンスを参照しているかの比較になるが、C#の場合は裏でstring.equalsを呼び出しているためこの書き方でも値の比較が可能。
String.equals(String, StringComparison)
という比較方法の指定が可能なメソッドもあるため、コードを書く時はこちらを使用するのが無難と感じた…が、やっていることが同じならコード量の少ない等値演算子がベターか。(2020.07.17 更新)参照→String.Equalsメソッド - .NET Tips|dobon.net
switch文
違いなし
Java
int month = 7; switch (month) { case 1: case 2: case 3: System.out.println("1Q"); break; case 4: case 5: case 6: System.out.println("2Q"); break; case 7: case 8: case 9: System.out.println("3Q"); break; case 10: case 11: case 12: System.out.println("4Q"); break; default: System.out.println("不正な値です。"); break; }C#
int month = 7; switch (month) { case 1: case 2: case 3: Console.WriteLine("1Q"); break; case 4: case 5: case 6: Console.WriteLine("2Q"); break; case 7: case 8: case 9: Console.WriteLine("3Q"); break; case 10: case 11: case 12: Console.WriteLine("4Q"); break; default: Console.WriteLine("不正な値です。"); break; }for文
Javaの拡張for文はC#ではforeachとなる。
Java
for(int i = 0; i <= 10; i++){ System.out.println(str); } String[] strArgs = {"1", "2"}; for(String str : strArgs){ System.out.println(str); }C#
for(int i = 0; i <= 10; i++){ Console.WriteLine(str); } string[] strArgs = {"1", "2"}; foreach(string str in strArgs){ Console.WriteLine(str); }while文
前判定、後判定共に違いなし。
Java
int i = 0; while (i < 5) { System.out.println(i); //0,1,2,3,4が出力される i++; } do { System.out.println(i); //0,1,2,3,4が出力される i++; } while (i < 5);C#
int i = 0; while (i < 5) { Console.WriteLine(i); //0,1,2,3,4が出力される i++; } do { Console.WriteLine(i); //0,1,2,3,4が出力される i++; } while (i < 5);参考
- 投稿日:2020-07-13T12:00:26+09:00
あなたも使ってるDIから理解するDIパターン
こう言うと「私はDIコンテナーなんて使っていない!アンチDIだ!」とおっしゃる人もいるかもしれません。
まぁ落ち着いてください。今回は、DIコンテナーは登場せず、Dependency Injection(以後DI)パターンのお話です。
DIパターンとは、つぎのようなものだと私は考えています。
「依存性を外部から注入することで、ふるまいを変更する設計パターン」
詳細はコードを見つつ解説します。コードはC#で記載していますが、だれでも読めるレベルです。たぶん言語が違えど似たようなコードは誰もが書いたことがあるはずです。
ということで、さっそく見ていきましょう!
あなたも使っているDIパターン
お題
「何らかのリソースから文字列を読み取り、コンソールに出力する」
もうネタはバレたかもしれません。
ひとつめのDI
さて、まずはローカルストレージ上のテキストファイルを読み込んでコンソールに出力してみましょう。以下のコードをご覧ください。
class Program { static void Main(string[] args) { // ローカルの「README.txt」ファイルを読み取り専用で開く using var stream = new FileStream("README.txt", FileMode.Open); WriteConsole(stream); } static void WriteConsole(Stream stream) { // ストリームから文字列を読みだすため、StreamReaderを生成する using var reader = new StreamReader(stream); Console.WriteLine(reader.ReadToEnd()); } }シンプルなコードですが、明らかにDIパターンが適用された設計になっています。詳しく見ていきましょう。
ここでは代表的なクラスとして、Program、FileStream、Stream、StreamReaderの4つのクラスが登場します。それらの関係は、つぎのようになっています。
FileStreamはファイルへの入出力を提供するStreamの実装クラスです。
Streamは何らかのリソースへの入出力を提供する「ストリーム」を表す抽象クラスです。Streamは必ずしもテキストリソースだけを扱う訳ではなく、画像などのバイナリソースも扱うため、バイト列をもちいます。
StreamReaderクラスは、Streamからバイト列を取得し、デコードして利用者に文字列を提供します。StreamReaderクラスはバイト列をどのリソースからどのように取得するのか、一切関与しません。そのため抽象的なStreamのみに依存し、実装クラスであるFileStreamには依存しません。
Programクラスはこれらを組み合わせて、ローカルファイルを読み取ってコンソールへ出力しています。
StreamReaderに、抽象的な依存性(Stream)を注入しており、紛れもなくDIパターンが採用されています。
ふたつめのDI
さて、ある時ローカルファイルではなく、Web上のリソースをコンソール出力したくなったとします。
そこで、あなたはつぎのようにコードを書き換えました。
class Program { static async Task Main(string[] args) { //using var stream = new FileStream("README.txt", FileMode.Open); // HttpClientを利用してURL「https://www.google.com/」上のリソースを開く using var httpClient = new HttpClient(); await using var stream = await httpClient.GetStreamAsync("https://www.google.com/"); WriteConsole(stream); } static void WriteConsole(Stream stream) { // ストリームから文字列を読みだすため、StreamReaderを生成する using var reader = new StreamReader(stream); Console.WriteLine(reader.ReadToEnd()); } }FileStreamの生成をコメントアウトし、HttpClientのGetStreamAsyncメソッドを利用して、指定アドレスからバイト列を読み取るためのStreamを非同期に取得します。
クラス間の関係はつぎのようになっています。
ここでもHttpClientから取得された抽象的なStreamを、StreamReaderに注入しており、DIパターンが踏襲されていることが見て取れます。
あなたも使っているDI
こんなパターンのDIであれば、あなたも一度は利用したことがあるのではないでしょうか?
実際、こういったパターンの設計は標準ライブラリにもよく見られます。オブジェクト指向言語をつかっている方であれば、どこかでDIをつかっているはずです。
別に構えるほど特別なものではないことに、共感いただけるのではないでしょうか。
ところで、本エントリーではDIコンテナーは登場しません。こんな言葉はありませんが「手組みDIパターン」です。DIコンテナーはDIパターンを利用するための道具であって、DIパターンを構成する必須要素ではありません。
あらためてDIパターンとは何か?
DIパターンの特徴
DIパターンとは、抽象的な依存性を、外部から注入することで、ふるまいを変える設計パターンです。
DIパターンの目的
DIパターンで、ふるまいを変える「目的」はつぎのものを得るためです。
- 再利用性
- 拡張性
- 保守性(レイヤー間の疎結合など)
- テスト容易性
など、ほかにもあります。英語のWikiが良くまとまっているので見てみるのも良いでしょう。
これらの目的を実現するための代表的なひとつの「手段」がDependency Injection Patternです。
DIパターンと同じ目的を実現する他の手段
もちろん手段はひとつではありません。
DIパターンの対抗となる代表的なパターンはService Locatorパターンです。これはDIが誕生した当初から議論されていることです。FactoryなどもService Locatorと大きな違いはありません。
これらを比較したときのメリット・デメリットは簡単には語り切れませんが、ここでは代表的なケースについて簡単に記載します。
DIのデメリット
Service Locatorと比較したとき、「難しい」ことだと私は思っています。習熟するとその難しさから遠ざかってしまいがちですが、そこから目をそらすべきではないでしょう。
Service Locatorパターンは依存先のオブジェクトを利用する箇所で、依存先のオブジェクトを構築(もしくは取得)します。
対してDIパターンでは、依存先オブジェクトを利用する個所と、依存先オブジェクトを構築する個所が分離しています。
これがDIパターンを難しくしている本質です。
Service Locatorパターンでは普通にオブジェクトをnewして利用する代わりに、Service Locatorから取得して利用するだけで、そこに大きなパラダイムの変化はありません。これはDIと比較して「簡単な」解決策です。
DIのメリット
逆にService Locatorでは解決できないケースもあります。そして今回のケースはこれに該当します。
DIやService Locatorの目的は「抽象的な依存性を切り替えることにより、ふるまいを変えること」です。
しかしService Locatorの場合、ふるまいを変えられる「幅」に、DIよりも制限があります。
「ファイルとWeb上のリソースを読み取るためのリーダークラス」はService Locatorパターンでも作れるかもしれません。ファイルのアドレスもURLも文字列ですしね。
しかし「開発対象のシステム専用のBLOBストレージに格納された、バイナリオブジェクトを読み取れるよう拡張できるStreamReaderクラス」を作ることはService Locatorパターン単独で解決することは難しいでしょう。DIよりトリッキーなコードか、Service Locator(依存性)をInjectionするか、いずれか必要になりそうです。もちろんこの例のStreamReaderであれば、専用のStreamさえ作れば簡単に実現できます。
Service Locatorの難しさ
DIは難しいと書きましたが、逆にService Locatorの方が難しくなるケースもあります。
とくにユニットテストでは顕著です。
Service LocatorでMockを解決しようとした場合、依存オブジェクトの利用箇所から分離された箇所で、Mockに差し替える必要があります。これは先に書いたDIの難しさとまったく同じものです。とはいえ、DIよりはそれらの個所は近いです。
またテストケースをマルチスレッドで実行したいといった場合、Service Locatorをマルチスレッド対応する必要があります。Thread-Specific Storageパターンを利用して解決できるでしょうけど、「難しい」話しです。
まとめ
- DIパターンとは、依存性を外部から注入することで、ふるまいを変えるパターンです
- DIパターンの目的は、以下を得ることです
- 再利用性
- 拡張性
- 保守性(レイヤー間の疎結合など)
- テスト容易性
- などなど
- 多くはService Locatorパターンなどで代替が可能ですが、代替できないケースもあります
- 依存性の利用箇所だけ見ると、Service Locatorパターンの方が簡単です
- テストを考慮するとDIの方が簡単なこともよくあります
- DIコンテナーはDIパターンをサポートするツールで、DIパターンそのものではありません
結局は使い分けなんですが、個人的にはService Locatorじゃないといけない場合を除き、DIパターンを利用することが多いです。「慣れれば」そんなに難しいものではないですし、過去のXML Hellみたいなことは現代のDIにはありませんしね。
ということで以上です。よいDIライフを!
- 投稿日:2020-07-13T07:43:58+09:00
【C#】IEnumerableの遅延評価でプロパティが書き換わらない
プロパティの値を変更したけど変わらない・・・
職場においてC#を利用して自社サービスを実装しているのですが、
IEnumerable
ではまったことをちょっとまとめておきたいと思います。下記は起こった事象を簡単にしたものとなります。下記の例は、
IEnumerable
に入ったユーザ情報の名前が大文字になることを期待したものとなります。// このようなクラスを読み込んでいます //class User { // public string Name { get; set; } //} IEnumerable<User> users = new[] { "kato", "saito", "kondo" } .Select(n => { return new User { Name = n }; }); foreach (User user in users) { // 大文字に変換したはずなのに・・・ user.Name = user.Name.ToUpper(); } foreach (User user in users) { Console.WriteLine(user.Name); }出力結果
kato saito kondoなぜか最初の値のままでした・・・
そうなんです。参照が違っておりました。
Enumerable.Repeat
とかEnumerable.Select
とかが遅延評価のIEnumerable
を返すので、foreach
やToList
で評価された際に初期化されてしまいます。修正パターン1
IEnumerable<User> users = new[] { "kato", "saito", "kondo" } .Select(n => { return new User { Name = n }; }).ToList(); foreach (User user in users) { // 大文字に変換 user.Name = user.Name.ToUpper(); } foreach (User user in users) { Console.WriteLine(user.Name); }ToList()することで再度評価されても同じ参照が返るので無事、大文字にできました。
KATO SAITO KONDO修正パターン2
修正パターン1だと、2ループしてしまうのは性能上問題があると思いますので、下記の方法を考えました。
IEnumerable<User> users = new[] { "kato", "saito", "kondo" } .Select(n => { return new User { Name = n }; }); users = users.Select(u => { u.Name = u.Name.ToUpper(); return u; }); foreach (User user in users) { Console.WriteLine(user.Name); }初期化の後にSelect()に修正して、無事、
KATO SAITO KONDO大文字に置換することができました!
実行環境
- C# 7.3
- Windows 10
- 投稿日:2020-07-13T06:00:48+09:00
Unity上でPlayボタンを押すとオブジェクトのAlbedoが変わるスクリプト
やりたいこと
Fusion360で作成したオブジェクトと、Substance Painterで3DペイントしたテクスチャをUnityに取り込んで、Playボタンを押すとテクスチャが適用されるスクリプトを作成します。
流れ
- Fusion360でオブジェクトを作成して、FBXでエクスポートする
- RizomUVでUV展開する
- Substance Painterで3Dペイントして、Unity用にエクスポートする
- Unityでオブジェクトとテクスチャをインポートする
- Albedoを適用するスクリプトを作成する ← 今回の投稿の対象
- スクリプトとオブジェクトを紐づける ← 今回の投稿の対象
- スクリプトとテクスチャ画像を紐づける ← 今回の投稿の対象
- 実行ボタンを押すと、オブジェクトのAlbedoが変更される ← 今回の投稿の対象
- 実行ボタンを押すと、Albedoが変わる ← 今回の投稿の対象
詳細
3Dモデルやテクスチャの取込方法については、以下の投稿を参考にしてください。
Substance PainterでエクスポートしたテクスチャをUnityに取り込む5. Albedoを適用するスクリプトを作成する
Hierarchyウィンドウの取り込んだオブジェクトをクリックし、InspectorウィンドウのAdd Component -> New Scriptを選択します。名称をtexture_changeに変更し、Create & addをクリックします。スクリプトが生成されました。
Assets内に生成されたスクリプトtexture_changeをダブルクリックし、Visual Studioを起動、以下のソースに変更し、保存します。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class texture_change : MonoBehaviour { // Start is called before the first frame update // スクリプトから画像を扱う場合に Texture または Spriteという形式に変換する必要がある // クラス変数として Texture型のクラス変数 wood_dark_AlbedoTransparencyを定義する public Texture wood_dark_AlbedoTransparency; void Start() { GetComponent<Renderer>().material.mainTexture = wood_dark_AlbedoTransparency; } // Update is called once per frame void Update() { } }6. スクリプトとオブジェクトを紐づける
5で作成したスクリプトを3Dオブジェクトに関連付け(アタッチ)します。スクリプトを選択し、3Dオブジェクト(この例ではBody1)にドラッグ&ドロップします。
7. スクリプトとテクスチャ画像を紐づける
Unityでは変数のアクセス修飾子をpublicにすると、インスペクター上で変数の紐づけを行うことができます。
Hierarchyウィンドウのスクリプトを紐づけた3Dオブジェクト(この例ではBody1)をクリックします。
Inspetorウィンドウを見ると、スクリプトに記載した変数が表示されています。変数の右の丸をクリックし、表示したいAlbedo画像を選択します。
8. 実行ボタンを押すと、オブジェクトのAlbedoが変更される
- 投稿日:2020-07-13T00:12:41+09:00
C#の記事を参考にしてPowershellで何かしたいときに読むメモ(スターターキット)
Powershellで.NETを扱う
C#の記事を参考にしてPowershellで.Netを扱いたい場合の覚え書きです。
環境
PS /workspaces> $PSVersionTable.PSVersion Major Minor Patch PreReleaseLabel BuildLabel ----- ----- ----- --------------- ---------- 7 0 2実行エンジン… .NET Core 3.1.5
参考:v7.0.2 Release of Powershell入門
その前に:インテリセンス(補完)機能
Powershellのコマンド名はとにかく長い。そして、
.NET
のクラスの名前空間なども長い。
ただし、インテリセンス(補完)機能は割と強力。名前空間が分からないクラスを探すときなども便利。
もちろんコマンドレットでもインテリセンスを使える。
Windowsの場合は
Ctrl+Spacebar
で最初から使用可能。
Linuxの場合はデフォルトでは設定されてないので$profile
等に設定コマンドを記述して使えるようにする。# 基本的にコマンドが割り当てられてないキーなら何処でも良い。 Set-PSReadLineKeyHandler -Chord Alt+q -Function MenuComplete※この記事はPowershellの補完機能を前提にしています。
Get-Member
数値
PS /workspaces> 17 | gm # Get-Memberのエイリアス
実行結果
TypeName: System.Int32 Name MemberType Definition ---- ---------- ---------- CompareTo Method int CompareTo(System.Object value), int CompareTo(int value), int IComparable.CompareTo(System.Object obj), int IComparable[int].CompareTo(int other) Equals Method bool Equals(System.Object obj), bool Equals(int obj), bool IEquatable[int].Equals(int other) GetHashCode Method int GetHashCode() GetType Method type GetType() GetTypeCode Method System.TypeCode GetTypeCode(), System.TypeCode IConvertible.GetTypeCode() ToBoolean Method bool IConvertible.ToBoolean(System.IFormatProvider provider) ToByte Method byte IConvertible.ToByte(System.IFormatProvider provider) ToChar Method char IConvertible.ToChar(System.IFormatProvider provider) ToDateTime Method datetime IConvertible.ToDateTime(System.IFormatProvider provider) ToDecimal Method decimal IConvertible.ToDecimal(System.IFormatProvider provider) ToDouble Method double IConvertible.ToDouble(System.IFormatProvider provider) ToInt16 Method short IConvertible.ToInt16(System.IFormatProvider provider) ToInt32 Method int IConvertible.ToInt32(System.IFormatProvider provider) ToInt64 Method long IConvertible.ToInt64(System.IFormatProvider provider) ToSByte Method sbyte IConvertible.ToSByte(System.IFormatProvider provider) ToSingle Method float IConvertible.ToSingle(System.IFormatProvider provider) ToString Method string ToString(), string ToString(string format), string ToString(System.IFormatProvider provider), string ToString(string format, System.IFormatProvider provider), stri… ToType Method System.Object IConvertible.ToType(type conversionType, System.IFormatProvider provider) ToUInt16 Method ushort IConvertible.ToUInt16(System.IFormatProvider provider) ToUInt32 Method uint IConvertible.ToUInt32(System.IFormatProvider provider) ToUInt64 Method ulong IConvertible.ToUInt64(System.IFormatProvider provider) TryFormat Method bool TryFormat(System.Span[char] destination, [ref] int charsWritten, System.ReadOnlySpan[char] format, System.IFormatProvider provider)文字列
PS /workspaces> 'Foo' | gm
実行結果
TypeName: System.Int32 Name MemberType Definition ---- ---------- ---------- CompareTo Method int CompareTo(System.Object value), int CompareTo(int value), int IComparable.CompareTo(System.Object obj), int IComparable[int].CompareTo(int other) Equals Method bool Equals(System.Object obj), bool Equals(int obj), bool IEquatable[int].Equals(int other) GetHashCode Method int GetHashCode() GetType Method type GetType() GetTypeCode Method System.TypeCode GetTypeCode(), System.TypeCode IConvertible.GetTypeCode() ToBoolean Method bool IConvertible.ToBoolean(System.IFormatProvider provider) ToByte Method byte IConvertible.ToByte(System.IFormatProvider provider) ToChar Method char IConvertible.ToChar(System.IFormatProvider provider) ToDateTime Method datetime IConvertible.ToDateTime(System.IFormatProvider provider) ToDecimal Method decimal IConvertible.ToDecimal(System.IFormatProvider provider) ToDouble Method double IConvertible.ToDouble(System.IFormatProvider provider) ToInt16 Method short IConvertible.ToInt16(System.IFormatProvider provider) ToInt32 Method int IConvertible.ToInt32(System.IFormatProvider provider) ToInt64 Method long IConvertible.ToInt64(System.IFormatProvider provider) ToSByte Method sbyte IConvertible.ToSByte(System.IFormatProvider provider) ToSingle Method float IConvertible.ToSingle(System.IFormatProvider provider) ToString Method string ToString(), string ToString(string format), string ToString(System.IFormatProvider provider), string ToString(string format, System.IFormatProvider provider), stri… ToType Method System.Object IConvertible.ToType(type conversionType, System.IFormatProvider provider) ToUInt16 Method ushort IConvertible.ToUInt16(System.IFormatProvider provider) ToUInt32 Method uint IConvertible.ToUInt32(System.IFormatProvider provider) ToUInt64 Method ulong IConvertible.ToUInt64(System.IFormatProvider provider) TryFormat Method bool TryFormat(System.Span[char] destination, [ref] int charsWritten, System.ReadOnlySpan[char] format, System.IFormatProvider provider)メソッド実行
PS /workspaces> 'Foo'.ToUpper() FOOキャスト
幾つか方法がある。
PS /workspaces> 'Foo' -as [char[]] F o o PS /workspaces> [char[]]'Foo' F o o多段キャスト
この場合は
as
演算子を使った方がわかりやすい気がする。PS /workspaces> [int[]][char[]]'Foo' 70 111 111 PS /workspaces> 'Foo' -as [char[]] -as [int[]] 70 111 111組み合わせる
※
Foreach()
はコレクションで使用可能なPowershell固有の特殊メソッド。
他にはClear()
やWhere()
がある。
Powershell7では差が縮まったがForeach-Object
よりもパフォーマンスに優れる。配列について知りたかったことのすべて
Methods of arraysPS /workspaces> 'Foo' -as [char[]] -as [Byte[]] | ForEach-Object {$_ + 10 -as [char]} | Join-String Pyy PS /workspaces> ('Foo' -as [char[]] -as [Byte[]]).ForEach{$_ + 10 -as [char]} -join '' Pyyオブジェクトを生成する方法
Powershellでオブジェクトを扱う方法は幾つもある。
公式の解説はこちら。
About Object Creationこの記事で扱うのは以下の通り。
- 静的メソッド
new()
でクラスのコンストラクタを実行する- 連想配列からキャストする
New-Object
を使うこの記事では詳しく扱わないが
System.Activator
のCreateInstance()
を使う方法もある。PS > $list=[System.Activator]::CreateInstance([System.Collections.Generic.List[int]]) PS > $list.Count 0 PS > $list.AddRange([int[]]@(1..10)) PS > $list[4..7] 5 6 7 8 PS > $list=[System.Activator]::CreateInstance([System.Collections.Generic.List[int]], [int[]]@(1..5)) PS > $list 1 2 3 4 5静的メソッド
new()
でコンストラクタを呼び出すPowershell5以降、クラスのコンストラクタは静的メソッド
new()
で呼び出せる。
C#のnew
に相当する。コンストラクタ有無はGetConstructors().Count
で確認可能。PS > [string].GetConstructors().Count 9 PS > [string]::new OverloadDefinitions ------------------- string new(char[] value) string new(char[] value, int startIndex, int length) string new(System.Char*, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e value) string new(System.Char*, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e value, int startIndex, int length) string new(System.SByte*, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e value) string new(System.SByte*, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e value, int startIndex, int length) string new(System.SByte*, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e value, int startIndex, int length, System.Text.Encoding enc) string new(char c, int count) string new(System.ReadOnlySpan[char] value)そのため、
new()
のPSMethod
のName
プロパティは.NET IL (中間言語)のコンストラクタメソッドである.ctor
となっている。PS > [string]::new | Get-Member TypeName: System.Management.Automation.PSMethod Name MemberType Definition ---- ---------- ---------- Copy Method System.Management.Automation.PSMemberInfo Copy() Equals Method bool Equals(System.Object obj) GetHashCode Method int GetHashCode() GetType Method type GetType() Invoke Method System.Object Invoke(Params System.Object[] arguments) ToString Method string ToString() IsInstance Property bool IsInstance {get;} MemberType Property System.Management.Automation.PSMemberTypes MemberType {get;} Name Property string Name {get;} OverloadDefinitions Property System.Collections.ObjectModel.Collection[string] OverloadDefinitions {get;} TypeNameOfValue Property string TypeNameOfValue {get;} Value Property System.Object Value {get;set;} PS > [string]::new.name .ctor※通常、メソッド名と
Name
プロパティは一致している。PS > [string]::Compare.name Compare PS > [string]::Concat.name Concat暗黙の型変換があるのでメソッドの引数はある程度柔軟に記述出来る。
明示的にキャストしなくても動作するPS > 'Bar'.GetType() IsPublic IsSerial Name BaseType -------- -------- ---- -------- True True String System.Object PS > [char[]]'Bar' B a r PS > [string]::new([char[]]'Bar') Bar PS > [string]::new('Bar') Bar連想配列からキャストする方法
引数なしコンストラクタがある場合、連想配列からインスタンスを生成出来る。
この方法を使うとそのクラスのプロパティを補完入力できる。プロパティを複数設定する場合は改行するか
;
で区切る。
改行だけでも動作はするが、補完入力は;
で区切る時のみ有効になる。
New-Object
を使う方法次のような場面で使用する。
- Comobjectを扱う場合...面倒な部分をラップしてくれる。
- 引数ありコンストラクタとプロパティの設定を同時に行う場合
# @を書き忘れるとエラーになる PS ❯ $WshShell = New-Object -ComObject WScript.Shell -Property {CurrentDirectory="D:\"} New-Object: Cannot bind parameter 'Property'. Cannot convert the "CurrentDirectory="D:\"" value of type "System.Management.Automation.ScriptBlock" to type "System.Collections.IDictionary". ⨯ PS ❯ $WshShell = New-Object -ComObject WScript.Shell -Property @{CurrentDirectory="D:\"} PS ❯ $WshShell.CurrentDirectory D:\usingについて
公式ドキュメント:about_Using - PowerShell | Microsoft Docs
Powershellのusingは3つの使い方がある。
using namespace <.NET-namespace> # 指定したnamecpaceを省略出来るようになる using module <module-name> # Powershellモジュールで定義されたクラスを利用出来るようにする using assembly <.NET-assembly-path> # 指定したアセンブリのクラスを継承したクラスを作成するために使う
using assembly
はclass
構文で使われる。Genericクラスの書き方
PS > using namespace System.Collections.Generic PS > $list=[List[int]]::new() PS > $list.add(12) PS > $list 12 PS > $dic=[Dictionary[string,System.Diagnostics.Process]]::new() PS > $dic.Count 0 PS > Get-Process | ForEach-Object {$dic.TryAdd($_.ProcessName,$_) > $null} PS > $dic.Count 140(7月13日(月)修正)Genericメソッドで型引数を指定する方法
Powershellは構文として型引数を設定したGenericメソッドの実行をサポートしていないので
MethodInfo.MakeGenericMethod(Type[])
を利用する。参考
MethodInfo.MakeGenericMethod(Type[]) メソッド (System.Reflection) | Microsoft Docs
vors/GenericMethods.ps1PS > $OfTypeInt=[System.Linq.Enumerable].GetMethods().where{$_.IsGenericMethod -and $_.name -eq 'Oftype'}.MakeGenericMethod([int]) PS > $OfTypeInt.Invoke($null,(,@(1,2,'a'))) 1 2 PS > $OfTypeInt.Invoke($null,@(1,2,'a')) MethodInvocationException: Exception calling "Invoke" with "2" argument(s): "Parameter count mismatch."ただし、厳密に型を合わせることにより、型引数の設定は省略が出来る。
Powershellではサポートしていないので ...
ぢつは、厳密に型を合わせれば、呼べなくもない
using namespace System.Linq $a0 = 1..5 $fn = { param($x) $x * 10 } [Enumerable]::Select($a0, $fn -as [Func[object, object]])公式ドキュメントの関連箇所(推定)
型引数は省略することもできます。コンパイラが推定します。 次は、前の呼び出しと同じように Swap を呼び出します。
ジェネリック メソッド - C# プログラミング ガイド | Microsoft Docs実践~MSDNの記事を利用する~
お題は
Form
の記事のサンプルコード
Form クラス (System.Windows.Forms) | Microsoft Docsその1:そのままPowershellに置き換える
new()
とusing namespace
でほぼ公式通りに記述することが可能。using namespace System.Drawing using namespace System.Windows.Forms Add-Type -AssemblyName System.Windows.Forms # コンストラクタ # Form作成 $form1 = [Form]::new() # ボタン作成 $button1 = [Button]::new() $button2 = [Button]::new() # ボタンの設定 # サンプルコードとの違い…DialogResultは明示的に設定 $button1.Text = "OK" $button1.DialogResult=[DialogResult]::OK $button1.Location = [Point]::new(10, 10) $button2.Text = "Cancel" $button2.DialogResult=[DialogResult]::Cancel $button2.Location = [Point]::new( $button1.Left, $button1.Height + $button1.Top + 10 ) # Formの設定 $form1.Text="My Dialog Box" $form1.HelpButton =$true $form1.FormBorderStyle=[FormBorderStyle]::FixedDialog $form1.MaximizeBox=$false $form1.MinimizeBox=$false # 生成したボタンを設定 $form1.AcceptButton=$button1 $form1.CancelButton=$button2 # 表示位置 $form1.StartPosition=[FormStartPosition]::CenterScreen # 合体 $form1.Controls.Add($button1) $form1.Controls.Add($button2) # 表示 $form1.ShowDialog()その2:Powershellで書きやすいように書く
連想配列を使ったオブジェクト生成を利用するとプロパティ設定をまとめやすい。
AddRange()
が使える場合は書き換えを検討する。using namespace System.Drawing using namespace System.Windows.Forms Add-Type -AssemblyName System.Windows.Forms $button1=[Button]@{ Text="OK"; DialogResult=[DialogResult]::OK; Location=[Point]::new(10,20); } $button2=[Button]@{ Text="Cancel"; DialogResult=[DialogResult]::Cancel; Location=[Point]@{ X=$button1.Left; Y=$button1.Height + $button1.Top + 10; }; } $form1=[Form]@{ Text="My Dialog Box"; HelpButton=$true; FormBorderStyle=[FormBorderStyle]::FixedDialog; MaximizeBox=$false; MinimizeBox=$false; StartPosition=[FormStartPosition]::CenterScreen; AcceptButton=$button1; CancelButton=$button2; } $form1.Controls.AddRange(@($button1,$button2)) $form1.ShowDialog()インテリセンスが効かなくなるが
;
が無くても動作する。using namespace System.Drawing using namespace System.Windows.Forms Add-Type -AssemblyName System.Windows.Forms $button1=[Button]@{ Text="OK" DialogResult=[DialogResult]::OK Location=[Point]::new(10,20) } $button2=[Button]@{ Text="Cancel" DialogResult=[DialogResult]::Cancel Location=[Point]@{ X=$button1.Left Y=$button1.Height + $button1.Top + 10 } } $form1=[Form]@{ Text="My Dialog Box" HelpButton=$true FormBorderStyle=[FormBorderStyle]::FixedDialog MaximizeBox=$false MinimizeBox=$false StartPosition=[FormStartPosition]::CenterScreen AcceptButton=$button1 CancelButton=$button2 } $form1.Controls.AddRange(@($button1,$button2)) $form1.ShowDialog()Powershellだと難しいこと
次のような場合は再現は難しくなったり面倒になったりする。
IL、リフレクションを使えば大体のことは出来るが手間がかかる。
- ラムダ式…Powershellにはラムダ式はないため。なお、Linqは静的メソッドで利用可能。
- 自動生成コードが仕事をしている場合。WPFなどが該当。裏で自動生成されている箇所も手動で処理する必要がある。そういう部分をラップするのもPowershellモジュールの役目。
参考
High Performance PowerShell with LINQ
PowershellでLinqするためのレシピ。個人的にSum()
をよく使う。(Measure-Object
よりも簡潔に書けるから)終わりに
- Powershellの生命線は補完機能だと思っている。
- 機会があったら
Register-ArgumentCompleter
について掘り下げてみたい。公式ドキュメント:Register-ArgumentCompleter (Microsoft.PowerShell.Core) - PowerShell | Microsoft Docs
次回があったら書きたいこと
イベント、
BackgroundJob
といった非同期の処理。
鍵を握るのは[scriptblock]
と型変換。※他の有力候補
- VScodeでPowershellの開発コンテナを立ち上げる
- VScodeでPowershellのハイブリッドモジュールを作成する
- PowershellとWPFとXAML、あとバインド