20200713のC#に関する記事は5件です。

Java技術者のためのC#チートシート

背景

これまでJavaをメインに仕事してきたのですが、ほとんど触れてこなかったC#を仕事で使うことになったので勉強しました。
似ているところもありますが、思想の違いや微妙な書き方の違いがあるので「これどう書くんだっけ?」が絶対に起きると思いましたので、よく使いそうなところをピックアップしてチートシートとしてまとめてみました。
細かいところは追々知っていこうと思います。

使用するパッケージの宣言

Java

呼び出し方が違う。

import java.util.ArrayList;

C#

using System.Collections.Generic;

パッケージの定義

宣言が違う。
また思想も違うのでネーミングは注意。

Java

package StudyPackage.com.anyplus

C#

namespace StudyPackage
// 思想が違うためドメインがない

アクセス修飾子

Javaにはないinternalprotected internalが存在する。
同じ修飾子でも思想の違いによることでアクセス可能範囲が異なる事にも注意。

修飾子 Java C#
public どこからでもアクセス可能 どこからでもアクセス可能
protected 同一のパッケージ、または派生したクラスからアクセス可能 派生したクラスからアクセス可能
internal 存在しない 同一のアセンブリ(DLL)内でアクセス可能
protected internal 存在しない 同一のアセンブリ(DLL)内、または格納しているクラスから派生した型からのみアクセス可能
private 同じクラス内からのみアクセス可能 同じクラス内からのみアクセス可能
指定なし
(default)
同一のパッケージ内でアクセスが可能 privateと同じ扱い

同一のアセンブリ(DLL)とは
同一アセンブリとは、同一のexeファイルや、DLLファイルの事。
VisualStudioのソリューション内であれば、同一プロジェクトということらしい。

継承

継承するときは:(コロン)を使用する。
オーバーライドさせたいメソッドにはvirtualを付け、オーバーライドする時はoverrideをつける。
virtualoverrideを付けないと、インスタンスが生成されて利用可能状態となる。
オーバーライドしたくない時は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);

参考

C#とJavaの比較
C# 入門

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

あなたも使ってる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.jpg

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.jpg

ここでも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ライフを!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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を返すので、foreachToListで評価された際に初期化されてしまいます。

修正パターン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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity上でPlayボタンを押すとオブジェクトのAlbedoが変わるスクリプト

やりたいこと

Fusion360で作成したオブジェクトと、Substance Painterで3DペイントしたテクスチャをUnityに取り込んで、Playボタンを押すとテクスチャが適用されるスクリプトを作成します。

流れ

  1. Fusion360でオブジェクトを作成して、FBXでエクスポートする
  2. RizomUVでUV展開する
  3. Substance Painterで3Dペイントして、Unity用にエクスポートする
  4. Unityでオブジェクトとテクスチャをインポートする
  5. Albedoを適用するスクリプトを作成する ← 今回の投稿の対象
  6. スクリプトとオブジェクトを紐づける ← 今回の投稿の対象
  7. スクリプトとテクスチャ画像を紐づける ← 今回の投稿の対象
  8. 実行ボタンを押すと、オブジェクトのAlbedoが変更される ← 今回の投稿の対象
  9. 実行ボタンを押すと、Albedoが変わる ← 今回の投稿の対象

詳細

3Dモデルやテクスチャの取込方法については、以下の投稿を参考にしてください。
Substance PainterでエクスポートしたテクスチャをUnityに取り込む

5. Albedoを適用するスクリプトを作成する

Hierarchyウィンドウの取り込んだオブジェクトをクリックし、InspectorウィンドウのAdd Component -> New Scriptを選択します。名称をtexture_changeに変更し、Create & addをクリックします。スクリプトが生成されました。
image.png

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)にドラッグ&ドロップします。
image.png

7. スクリプトとテクスチャ画像を紐づける

Unityでは変数のアクセス修飾子をpublicにすると、インスペクター上で変数の紐づけを行うことができます。
Hierarchyウィンドウのスクリプトを紐づけた3Dオブジェクト(この例ではBody1)をクリックします。
Inspetorウィンドウを見ると、スクリプトに記載した変数が表示されています。変数の右の丸をクリックし、表示したいAlbedo画像を選択します。
image.png
image.png

8. 実行ボタンを押すと、オブジェクトのAlbedoが変更される

image.png

image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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のクラスの名前空間なども長い。
ただし、インテリセンス(補完)機能は割と強力。

IntelliSense1_command.gif

名前空間が分からないクラスを探すときなども便利。

IntelliSense2_class.gif

もちろんコマンドレットでもインテリセンスを使える。

IntelliSense3_commandlet.gif

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 arrays

PS /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

この記事で扱うのは以下の通り。

  1. 静的メソッドnew()でクラスのコンストラクタを実行する
  2. 連想配列からキャストする
  3. New-Objectを使う

この記事では詳しく扱わないがSystem.ActivatorCreateInstance()を使う方法もある。

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()PSMethodNameプロパティは.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

連想配列からキャストする方法

引数なしコンストラクタがある場合、連想配列からインスタンスを生成出来る。
この方法を使うとそのクラスのプロパティを補完入力できる。

process.png

プロパティを複数設定する場合は改行するか;で区切る。
改行だけでも動作はするが、補完入力は;で区切る時のみ有効になる。

SharedScreenshot.png

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 assemblyclass構文で使われる。

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.ps1

PS > $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]])

@ktz_aliasさんコメントより(一部抜粋)

公式ドキュメントの関連箇所(推定)

型引数は省略することもできます。コンパイラが推定します。 次は、前の呼び出しと同じように 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()

semicolon.png
nosemicolon.png

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、あとバインド
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む