質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
PowerShell

Windows PowerShellはコマンドラインインターフェースであり、システム管理を含むWindowsタスク自動化のためのスクリプト言語です。

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。

Q&A

解決済

2回答

2360閲覧

インデックスの指定に数値とNameの両方が使えるコレクション? を使いたい

kamikazelight

総合スコア305

PowerShell

Windows PowerShellはコマンドラインインターフェースであり、システム管理を含むWindowsタスク自動化のためのスクリプト言語です。

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。

1グッド

0クリップ

投稿2019/03/13 06:05

編集2019/03/13 07:50

前提・実現したいこと

オブジェクトを配列にして使っているのですが
インデックスの指定に数値とNameの両方が使えるようにしたいのです
何を利用したらいいのでしょうか

FormのControls 等 目にする機会は多いのですが
作り方が全く分かりません。
お願い致します。

該当のソースコード

powershell

1Using NameSpace System.Windows.Forms 2 3$Form = [Form]::new() 4 5for ($i=0;$i-le9;$i++) 6{ 7 [TextBox]::new() | 8 %{ 9 $_.Name = "TextBox_$i" 10 $Form.Controls.Add($_) 11 } 12} 13 14<# 15 どちらの方法でも取得できる 16 こんな感じで使えるコレクション?を作りたい 17#> 18Write-Host $($Form.Controls["TextBox_5"].Name) -ForegroundColor Red 19Write-Host $($Form.Controls[5].Name) -ForegroundColor Red

試したこと

現状だとディクショナリ 使って名前で指定できるようにして
数値で指定したくなったらValueでArray(object[])にするしか.....

PowerShellのクラスのプロパティでAdd-memberを使って
ゲッターセッター風にすることはできるですが
クラスのメンバーを追加するだけなので....
クラスのインスタンス自身にインデクサ?を指定されたときの振る舞いを
変えることは出来ないですし

powershellだと出来ないってことでしょうか?(Add-type で C#取り込み除く)

powershell

1Using NameSpace System.Collections.Generic 2Using NameSpace System.Management.Automation 3 4$Dic = [Dictionary[String,PSCustomObject]]::new() 5 6$Ar = 0..9 | 7%{ 8 [PSCustomObject]@{ 9 Name = "Name$_" 10 Text = "Text_$_" 11 Disp = "テキスト_$_" 12 } 13} 14 15$Ar | 16%{ 17 $Dic.Add($_.Name,$_) 18} 19 20 21Write-host $Dic["Name5"].Name -ForegroundColor Red 22Write-host $($Dic.Values)[5].Name -ForegroundColor Red

補足情報(FW/ツールのバージョンなど)

Win10
powershell 5.1

atata0319👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zuishin

2019/03/13 08:32 編集

例えばインデクサを二つ持つクラスを Add-Type で C# で作成し、それをプロパティとして持たせるというのもダメでしょうか?
kamikazelight

2019/03/13 09:10 編集

質問文 > powershellだと出来ないってことでしょうか?(Add-type > で C#取り込み除く) ですね 何年も気になっていたので質問したのですが 考え直しても 使えたら便利ですけど無くてもそこまで困るものでもないと思ったので やりたかったらAdd-type 使うしかないのであれば 諦めます。
Zuishin

2019/03/13 09:11

リフレクションを作って動的に型を作れば PowerShell だけでできないことはありません。 不必要に面倒になりますが。
Zuishin

2019/03/13 09:16 編集

TypeBuilder を使ってこんな感じでやりますが、IL を自分で書くか式木を組み立てるかの選択で、どっちも超めんどくさいです。 http://www.moonmile.net/blog/archives/8683 少し簡単にするには、C# で書いてコンパイルしてそれをディスアセンブルするということになりますが、それだと本末転倒な気もします。
kamikazelight

2019/03/13 09:23 編集

ありがとうございます。 リフレクション...ですか 前にリフレクションを使ってWinApi使う方法を見たことがありますが、それもかなり面倒なことになっていました.... リフレクション 自体何なのか全く分かっていませんが それに関わると恐ろしいことになるのはよく分かりました。 C# を利用する以上に簡単な方法はない ということでいいのでしょうか。
Zuishin

2019/03/13 10:04 編集

リフレクション自体は「少し面倒」くらいです。ただこの場合は動的メソッドを作らなければいけないので、普通のリフレクションの 100 倍面倒だと思っていただければ。要するにコンパイラのするべき仕事を手作業でするということです。 私はこの場合は C# が一番手軽だと思っていますし、ざっと検索してみてもインデクサについての情報はみつかりませんでしたが、知的好奇心ということであれば、よく探せばもしかしたら何かあるかもしれません。 また、インデクサ実体はメソッドなので、オブジェクトを引数とする ScriptMethod を作り、その内部でどうにかすれば似たようなことはできるかもしれません。その場合、取得は o.Items("John") o.Items(2) 設定は o.SetItem("John") のような形になります。 スクリプトメソッドの中では $this で自身のオブジェクトを取得できるので、データ自体は NoteProperty か何かで持たせておくことになります。
kamikazelight

2019/03/14 00:43 編集

> インデクサ実体はメソッド ということは インデクサは既定のメソッド っていう感じでしょうか? PowerShell のクラスで 既定のメソッド、又はプロパティって指定出来るのでしょうか? 一先ず教えて頂いたDynamicObject の使い方を確認するために 例文をそのまま powershell で書き直そうとしたのですが out や override の文法の代替?が分からず 使えませんでした。 ```powershell Using NameSpace System Using NameSpace System.Dynamic Using NameSpace System.Collections.Generic class DynamicDictionary : DynamicObject { [Dictionary[string, System.Object]] $dictionary = [Dictionary[string, Object]]::new() [int] Count () { return $This.dictionary.Count } <# 既定のメソッドと思われるが powerShell で C# の 「public override」 と同じ役割をする文法が分からない #> [bool] TryGetMember ( [GetMemberBinder] $binder, [Object] $result) { [string] $name = $binder.Name.ToLower() return $This.dictionary.TryGetValue($name,$result) } # 上に同じく [bool] TrySetMember( [SetMemberBinder] $binder, [Object] $value) { $This.dictionary[$binder.Name.ToLower()] = $value return $true } } class Program { static [void] Main([string[]] $args) { [DynamicDictionary] $person = [DynamicDictionary]::new() <# FirstName と LastName どっから降って湧いたのかわからない.... 定義してない.... そしてやっぱり使えない とゆうか $args どこ行ったの....? #> $person.FirstName = "Ellen" $person.LastName = "Adams" Write-Host "$($person.firstname) $($person.lastname)" Write-Host "Number of dynamic properties: $($person.Count)" } } clear [Program]::Main(@("A","B","C","D","E")) ```
Zuishin

2019/03/14 00:43 編集

既定のメソッドではなくインデクサとして定義されたプロパティから呼ばれるメソッドです。PowerShell の Class 構文では定義できないみたいですね。 DynamicObject も C# の言語機能が必要だったようで使えませんでした。
kamikazelight

2019/03/14 00:46 編集

なるほど... でも 勉強になりました。 OrderedDictionary が使えることが分かったので そちらを使ってみることにします。 今回もありがとうございました。
guest

回答2

0

ベストアンサー

既存のコレクションの中では、System.Collections.Specialized.OrderedDictionaryが意図したものに近いと思われます。

OrderedDictionary Class (System.Collections.Specialized) | Microsoft Docs

言語サポートにより、インスタンスおよび初期化がしやすいのもポイントです。
ただしジェネリック非対応のため、型を指定できない点には注意が必要です。

powershell

1# サンプル 2 3# インスタンス。 4# [ordered] 属性をハッシュテーブルリテラルに付ければOK([ordered] で変数の型は指定できないので注意) 5$Dic = [ordered]@{} 6 7# 中身を指定して初期化。 8# ハッシュテーブルリテラルでそのまま指定すればOK。 9$Dic1 = [ordered]@{ 10 a = 1 11 b = Get-Date 12 c = 'OrderedDictionary' 13 d = 1..10 14} 15<# 16Name Value 17---- ----- 18a 1 19b 2019/03/13 XX:XX:XX 20c OrderedDictionary 21d {1, 2, 3, 4...} 22#>

「試したこと」のコードをOrderedDictionaryを使って書くと以下のようになります。

powershell

1$Dic = [Ordered]@{} 2 3$Ar = 0..9 | 4%{ 5 [PSCustomObject]@{ 6 Name = "Name$_" 7 Text = "Text_$_" 8 Disp = "テキスト_$_" 9 } 10} 11 12$Ar | 13%{ 14 $Dic.Add($_.Name,$_) 15} 16 17 18Write-Host $Dic["Name5"].Name -ForegroundColor Red 19Write-Host $Dic[5].Name -ForegroundColor Red

その他参考

通常の辞書は、順番が保証されていません。
そのため、位置指定による中身へのアクセスが出来なくなっています。

わかりやすい例として、途中の要素を削除すると順序保証がされなくなります(このあたりの挙動は VBA の Scripting.Dictionary も同じ)。

powershell

1Using NameSpace System.Collections.Generic 2$dicN = [Dictionary[string,string]]::new() 3$dicN["a"] = "1" 4$dicN["b"] = "2" 5$dicN["c"] = "3" 6$dicN.Remove("b") > $null 7$dicN["d"] = "4" 8$dicN.Values 9<# 101 114 # 順番が変わる 123 13#>

この場合でもOrderedDictionaryなら順番を保証してくれます。

powershell

1$dicO = [ordered]@{} 2$dicO["a"] = "1" 3$dicO["b"] = "2" 4$dicO["c"] = "3" 5$dicO.Remove("b") 6$dicO["d"] = "4" 7$dicO.Values 8<# 91 103 114 # [2] の位置に追加される 12#>

参考記事

PowerShellのHashtableの並び替えを制御したい - tech.guitarrapc.cóm


190315 追記 KeyedCollection 継承クラス

KeyedCollection<TKey,TItem> Class (System.Collections.ObjectModel) | Microsoft Docs

KeyedCollection を適当に継承したクラスを作ってみましたが、一応動いているようです(自分の環境ではインテリセンスが不安定なのがちょっと気になりますが)。

KeyedCollection は抽象クラスなので、継承した上でGetKeyForItemメソッドを実装して、各要素からキーを取得できるようする必要があります。
今回はサンプルとして、コンストラクタに与えられたスクリプトブロックからキーを算出させるようにしています(設計としては色々怖いのであくまでサンプル)。

powershell

1using namespace System.Collections.ObjectModel 2using namespace System.Management.Automation 3 4class MyKeyedList : KeyedCollection[string, PSCustomObject] 5{ 6 # must override method. 7 hidden [string]GetKeyForItem([PSCustomObject]$TItem) { 8 return [string](ForEach-Object -InputObject $TItem -Process $this._keySelector) 9 } 10 hidden [scriptblock]$_keySelector 11 12 MyKeyedList( 13 # $_ 自動変数を使ってキーを算出するScriptBlcokを指定する。 14 [scriptblock]$KeySelector 15 ) : base() { 16 $this._keySelector = $KeySelector 17 } 18} 19 20# Name プロパティをキーとする 21$Dic = [MyKeyedList]::new({$_.Name}) 22 23$Ar = 0..9 | 24%{ 25 [PSCustomObject]@{ 26 Name = "Name$_" 27 Text = "Text_$_" 28 Disp = "テキスト_$_" 29 } 30} 31 32$Ar | 33%{ 34 $Dic.Add($_) 35} 36 37 38Write-Host $Dic["Name5"].Name -ForegroundColor Red 39Write-Host $Dic[5].Name -ForegroundColor Red

投稿2019/03/13 10:47

編集2019/03/14 16:02
imihito

総合スコア2166

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kamikazelight

2019/03/13 23:55 編集

まさに探していたものです 驚きの連続です! [Ordered]はハッシュテーブルの順番保持くらいにしか思っていませんでした。 今まで一度も自動補完をしたことが無かった(自動補完で出て来ないと思っていた)のでホントは[OrderedDictionary]という辞書だと知りませんでした。驚きです Addしなくても使えることにさらに驚きです.... インデックスに数値を指定するときも Values もカッコもいらないのも驚きです。 > 通常の辞書は、順番が保証されていません。 知りませんでした。 そのまま使っていたら痛い目にあっていたと思います。 順番大事なので...助かりました。 今回も本当に助かりました。 また宜しくお願い致します。
imihito

2019/03/14 12:49 編集

若干補足として > Addしなくても使えることにさらに驚きです.... `$dicN["a"] = "1"`の話と仮定しますが、 インデクサで代入した場合、意図しない上書きが発生する可能性があるため、その挙動の違いには注意が必要です。 Add で追加する場合は、キーが被ったときにエラーになりますが、インデクサで代入した場合は、そのキーの位置に要素があったとしても、上書きされてしまいます。 $dic = [ordered]@{} $dic["a"] = 1 $dic["a"] # => 1 $dic["A"] = 2 # 意図しない上書きの可能性 $dic["a"] # => 2 $dic.Add("a", 3) # Error! "項目は既に追加されています。"
kamikazelight

2019/03/14 23:38

状況に応じて使い分けます。 ありがとうございました。
kamikazelight

2019/03/15 00:04

KeyedCollection のことですが アイテムだけで登録できるのはとても魅力的です 教えて頂いた方法なら汎用性もあって使いまわせそうですね。 > 設計としては色々怖いのであくまでサンプル とのことですが どんなリスクがあるのでしょうか? 見た感じ キーが同じアイテムが追加出来てしまうのと それによって戻り値が複数になった場合が問題になるくらいでしょうか? そこまでだったら対応出来そうですが 他に何か地雷があるのでしょうか....?
kamikazelight

2019/03/15 00:23

すみません 同じキーは ちゃんと 登録できないようになっているのですね
kamikazelight

2019/03/15 00:56

_keySelector を 途中で書き換えてみても 最初に設定された内容から変わら無かったので 戻り値が複数になる場合 も なさそうですね.... とても安定しているように見えるのですが 地雷はあるのでしょうか?
imihito

2019/03/15 10:06

個人的に怖い部分は以下になります。 個人で使う分には影響が少ないとは思いますが……。 # ScriptBlock が String を返す保証が無い 最悪 Object.ToString() があるのでエラーになることは無いでしょうが、コンストラクタで渡される ScriptBlock に一切チェックを行うことができません。 (今試したら空の ScriptBlock を渡しても動いてしまったので、問題無いかもですが……) # _keySelector を途中で書き換えできる _keySelector を書き換えた場合でも元の要素は保持されますが、代わりに重複要素が許容されることになってしまいます。 _keySelector は原則読み取り専用で、コンストラクタ以外では変更出来ないのが望ましい、という考えです。 また以下を見ると、各種メソッドでGetKeyForItemが使われている雰囲気のため、途中で変更されたときに挙動が統一されない可能性があります。 https://referencesource.microsoft.com/#mscorlib/system/collections/objectmodel/keyedcollection.cs,ced1024bdf76c9e1
kamikazelight

2019/03/16 05:57

_keySelector を途中で書き換えさえしなければ 大丈夫そうですね・・・? Ordered を使いつつどうしても使いたくなったら 使ってみることにします。 ありがとうございました。
guest

0

PowerShellは判らないのでC#の判るところだけ。

C#のソースは公開されていますので、そちらをみればどう実装されているかが一目瞭然です。
https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Control.cs,14954

14954行あたり

public new virtual Control this[int index] {...}
public virtual Control this[string key] {...}

というように、インデクサが2つ定義されているだけです。

カスタムクラスではわりとこのような事を実装しやすいですが、
何かしらの既存のコレクションクラスを使って似たような事を行いたい、というのは難しいかなと思います。

強いてえ言えば、List<Hoge>Dictionary<String, Hoge>というように2つのコレクションを用意しておいて、両方使う、という方法であれば楽に実装できるかと思います(数値では前者、文字列では後者で検索できるようにコレクションを準備しておく)。

投稿2019/03/13 06:48

takabosoft

総合スコア8356

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kamikazelight

2019/03/13 07:05

回答ありがとうございます。 インデクサ という単語を初めて知りました。 少し調べてみたのですが クラス自身をゲッターセッター付きのプロパティみたいに扱える機能って感じでしょうか.... PowerShell の クラス は プロパティのゲッターセッターすら無いので苦戦しそうな気がしますね.... いろいろ見てみます。
takabosoft

2019/03/13 07:20

簡単に言えば引数付きのゲッター・セッターですね。がんばってください。
kamikazelight

2019/03/13 08:11

ありがとうございます。 考えるほどpowershellにはそういった物が無さそう気がしてきました。 何年も気になっていたので質問したのですが 考え直しても 使えたら便利ですけど無くてもそこまで困るものでもないと思ったので 一日ほど考えて何もわからなければ閉じることにします。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問