C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

クラスの中のPropertyをFor Eachなどで回す方法

[トピック内 12 記事 (1 - 12 表示)]  << 0 >>

■89233 / inTopicNo.1)  クラスの中のPropertyをFor Eachなどで回す方法
  
□投稿者/ 魚魚 (5回)-(2018/11/10(Sat) 13:19:19)

分類:[.NET 全般] 

    Public Class TextColorClass

        Property AAA As Color

        Property BBB As Color
        Property CCC As Color

        Property DDD As Color
        Property ZZZ As New TextColorClass2

    End Class


    Public Class TextColorClass2

        Property EEE As Color
        Property FFF As Color

    End Class


上記のようにカラーが複数登録されたクラスを作成して、


Sub test()

 Dim xxx as New TextColorClass
 xxx.AAA = Color.Black
・・・
 xxx.ZZZ.EEE = Color.Black
・・・
End Sub


のようにして、このクラスに登録された全てのカラーをブラックにしたいと考えています。
クラスの中のPropertyをFor Eachなどで回すことはできないでしょうか?




引用返信 編集キー/
■89234 / inTopicNo.2)  Re[1]: クラスの中のPropertyをFor Eachなどで回す方法
□投稿者/ 魔界の仮面弁士 (1927回)-(2018/11/10(Sat) 21:08:47)
No89233 (魚魚 さん) に返信
> Public Class TextColorClass
>   Property ZZZ As New TextColorClass2
> End Class

これだと外部から
 xxx.ZZZ = Nohing
のように差し替えられてしまうので、ZZZ は
ReadOnly Property にしておいた方が良いのでは。


> クラスの中のPropertyをFor Eachなどで回すことはできないでしょうか?

呼び出し側で列挙するより、一括代入のために
 xxx.Reset(Color.Black)
のように呼び出せるメソッドを TextColorClass 自身に持たせるのはどうでしょう。

以下サンプル。リフレクションを用いているので、パフォーマンスは犠牲になります。
(速度を求める場合は、そもそも最初から Dictionary 等で管理した方が良いでしょう)


Public Class TextColorClass
  Property AAA As Color
  Property BBB As Color
  Property CCC As Color
  Property DDD As Color
  Property ZZZ As New TextColorClass2


  ''' <summary>
  ''' このクラスが持つすべての「Color を返すプロパティ」に値を代入します。
  ''' </summary>
  ''' <param name="c">代入する値。</param>
  Public Sub Reset(c As Color)
    Reset(Me, c)
  End Sub
  Private Sub Reset(o As Object, c As Color, Optional recurseDepth As Integer = 1)
    If o Is Nothing Then Return
    Dim properteis = o.GetType().GetProperties()
    'As Color な引数無しプロパティを列挙して、新しい色を代入する。
    For Each p In From q In properteis Where q.PropertyType Is GetType(Color) AndAlso q.CanWrite AndAlso q.GetSetMethod().GetParameters().Length = 1
      p.SetValue(o, c)
    Next
    If recurseDepth > 0 Then
      'As Color 以外を返す引数無しプロパティがあれば再帰的に処理する
      For Each p In From q In properteis Where q.PropertyType IsNot GetType(Color) AndAlso q.CanRead AndAlso q.GetGetMethod().GetParameters().Length = 0 Select q.GetValue(o)
        Reset(p, c, recurseDepth - 1)
      Next
    End If
  End Sub

End Class
引用返信 編集キー/
■89236 / inTopicNo.3)  Re[2]: クラスの中のPropertyをFor Eachなどで回す方法
□投稿者/ Jitta (424回)-(2018/11/12(Mon) 09:41:40)
コンストラクタで黒に初期化したいがいっぱいあるから一つ一つ設定するのが面倒。
ってこと?
Type 型から GetProperties してゴニョゴニョ。
敷居は高めなので、これで検索できないなら諦めた方がいいと思います。
引用返信 編集キー/
■89257 / inTopicNo.4)  Re[3]: クラスの中のPropertyをFor Eachなどで回す方法
□投稿者/ 魚魚 (6回)-(2018/11/13(Tue) 22:41:55)
ありがとうございます。

うまくいきました。
ちなみに、
Public Sub Reset(c As Color)
Private Sub Reset(o As Object, c As Color, Optional recurseDepth As Integer = 1)

のように全く同じ名前のSubルーチンが共存していますが
なぜエラーが出ないのでしょうか?
Publicの方をPrivateとかに変更してもやはりエラーは出ないのですが
なぜでしょうか?

引用返信 編集キー/
■89258 / inTopicNo.5)  Re[4]: クラスの中のPropertyをFor Eachなどで回す方法
□投稿者/ 魔界の仮面弁士 (1932回)-(2018/11/14(Wed) 00:47:38)
No89257 (魚魚 さん) に返信
> ちなみに、
> Public Sub Reset(c As Color)
> Private Sub Reset(o As Object, c As Color, Optional recurseDepth As Integer = 1)
>
> のように全く同じ名前のSubルーチンが共存していますが

気になるようならば、後者は Sub ResetInternal とでも改名してくださいませ。
役割は同種であるにせよ、振る舞いの異なるメソッドなので、実際は別の名前の方が良いでしょう。


> なぜエラーが出ないのでしょうか?

えぇと…(^^;

何故、って言われると、「そういうものだから」としか答えられないです。
引数定義の異なるのに、同じ名前のついているメソッドという意味では、
MessageBox.Show() メソッドや Console.WriteLine() メソッドだってそうですよね。


ちなみに、メソッドの引数定義(signature) だけが異なる同名メソッドのことを、
「メソッドのオーバーロード」と呼びます。

Visual Basic では明示的に
 Public Overloads Sub Reset(c As Color)
 Private Overloads Sub Reset(o As Object, c As Color, Optional recurseDepth As Integer = 1)
と書くこともありますね。


> ありがとうございます。
> うまくいきました。

一応注意点として:


(1) 『副作用のあるプロパティ』や『状態に応じて振る舞いのかわるプロパティ』の存在を考慮していません。

 ' Color なプロパティを持つ子クラスを返すプロパティ
 Public ReadOnly Property ZZZ As TextColorClass2
  Get
   ZZZ = _zzz(ExampleIndexValue)
  End Get
 End Property
 Private _zzz(0 To 15) As TextColorClass2
 
 '呼び出すたびに インクリメントされるプロパティ
 Public ReadOnly Property ExampleIndex As Integer
  Get
   ExampleIndexValue = (ExampleIndexValue + 1) And 15
    Return ExampleIndexValue
  End Get
 End Property
 Private ExampleIndexValue As Integer = 0


(2) 引数付きプロパティへの書き込みは行われません。

 Public Property IndexedColor(index As Integer) As Color
  Get
   Return _color(index And 15)
  End Get
  Set(newColor As Color)
   _color(index) = newColor
  End Set
 End Property
 Private _color(15) As Color
引用返信 編集キー/
■89259 / inTopicNo.6)  Re[5]: クラスの中のPropertyをFor Eachなどで回す方法
□投稿者/ furu (188回)-(2018/11/14(Wed) 10:50:53)
No89258 (魔界の仮面弁士 さん) に返信
> 一応注意点として:

すいません、横ヤリです。

private set (C#) でも書き込んでしまいますか?

昔、直接読み書きされたくないプロパティをリフレクションでやられて
「リフレクション使うな!」って怒ったのですが、社内ルールとかでなく
一般的にはリフレクションは気にせず使っていいものでしょうか?
引用返信 編集キー/
■89260 / inTopicNo.7)  Re[6]: クラスの中のPropertyをFor Eachなどで回す方法
□投稿者/ 魔界の仮面弁士 (1933回)-(2018/11/14(Wed) 12:17:29)
2018/11/14(Wed) 12:18:04 編集(投稿者)

No89259 (furu さん) に返信
> private set (C#) でも書き込んでしまいますか?

先のコードに関してはそうなっていませんが、やろうと思えば、
自動実装プロパティのバッキングフィールドにさえアクセスできます。


> 昔、直接読み書きされたくないプロパティをリフレクションでやられて
> 「リフレクション使うな!」って怒ったのですが、社内ルールとかでなく
> 一般的にはリフレクションは気にせず使っていいものでしょうか?

そもそもはリフレクションに頼らない方が望ましいですね。個人的にはお奨めしません。
特に外部からの利用は、カプセル化という視点から見ても避けるべきでしょう。

データバインド的な扱いのために、動的なメンバー呼び出しが
必要になることはありますが、それも稀なケースだと思いますし。


今回は、クラス設計者が自クラスにアクセスするだけだったので、
外部から利用するケースよりはマシかと思いますが、先述の副作用問題も
あるわけですから、実際は属性を指定してフィルタリングするなど、
追加の施策が必要になることもありそうです。
引用返信 編集キー/
■89261 / inTopicNo.8)  Re[7]: クラスの中のPropertyをFor Eachなどで回す方法
□投稿者/ furu (189回)-(2018/11/14(Wed) 12:30:57)
No89260 (魔界の仮面弁士 さん) に返信
ありがとうございます。
もうひとつ教えてください。

> 先のコードに関してはそうなっていませんが、
以下のようなプロパティの書込を対象外にしているのは
このコードでどの部分ですか?

public Color DefaultColor {get;private set;}
引用返信 編集キー/
■89262 / inTopicNo.9)  Re[8]: クラスの中のPropertyをFor Eachなどで回す方法
□投稿者/ 魔界の仮面弁士 (1934回)-(2018/11/14(Wed) 13:24:48)
2018/11/14(Wed) 14:12:52 編集(投稿者)

No89261 (furu さん) に返信
> もうひとつ教えてください。
これ以上はもはや横ヤリというよりも、
元質問者を置き去りにした乗っ取りですよ。
対象言語まで違うわけですし。


>>先のコードに関してはそうなっていませんが、
> 以下のようなプロパティの書込を対象外にしているのは
> このコードでどの部分ですか?
> public Color DefaultColor {get;private set;}

q.GetSetMethod() は非公開 setter を返却しない仕様だからです。
(なので、先のコードのままだと NullReferenceException になる)


先の列挙条件を
 q.GetSetMethod(nonPublic:true) // C#
 q.GetSetMethod(nonPublic:=True) ' VB
にすれば、private な setter にも書き込まれます。


===== 追記 =====

ちなみに
 Public ReadOnly Property DefaultColor As Color
だった場合には、そもそも q.CanWrite が False となるため、
自動的に書き込み対象から除外されます。


一方、Private Set なプロパティの場合は、q.CanWrite が True なので、
 q.CanWrite AndAlso q.GetSetMethod().GetParameters().Length = 1
の GetParameters の呼び出し時にエラーとなります。

これを避けるには、
 q.CanWrite AndAlso If(q.GetSetMethod(False)?.GetParameters()?.Length, -1) = 1
のようにします。
これなら、Private Set があってもエラーになりません。


同様に、
 q.CanRead AndAlso q.GetGetMethod().GetParameters().Length = 0
の方も、
 q.CanRead AndAlso If(q.GetGetMethod(False)?.GetParameters()?.Length, -1) = 0
にしておいた方が良いでしょう。



VB2008〜VB2013 の場合は、"?." 構文が使えないので、こうかな。

For Each p In From q In properteis Where q.PropertyType Is GetType(Color) AndAlso q.CanWrite Let m = q.GetSetMethod(False) Where m IsNot Nothing AndAlso m.GetParameters().Length = 1 Select q
引用返信 編集キー/
■89263 / inTopicNo.10)  Re[9]: クラスの中のPropertyをFor Eachなどで回す方法
□投稿者/ furu (190回)-(2018/11/14(Wed) 13:36:52)
No89262 (魔界の仮面弁士 さん) に返信
ありがとうございます。
失礼致しました。
引用返信 編集キー/
■89264 / inTopicNo.11)  Re[10]: クラスの中のPropertyをFor Eachなどで回す方法
□投稿者/ 魚魚 (7回)-(2018/11/14(Wed) 15:04:20)
ありがとうございます。

あと、
これだと外部から
 xxx.ZZZ = Nohing
のように差し替えられてしまうので、ZZZ は
ReadOnly Property にしておいた方が良いのでは。

これってどういうことですか?
ReadOnly Propertyにした方が良いという理由をもう少し詳しくお教えいただけないでしょうか?


引用返信 編集キー/
■89265 / inTopicNo.12)  Re[11]: クラスの中のPropertyをFor Eachなどで回す方法
□投稿者/ 魔界の仮面弁士 (1935回)-(2018/11/14(Wed) 15:45:46)
No89264 (魚魚 さん) に返信
> これだと外部から
>  xxx.ZZZ = Nohing
> のように差し替えられてしまうので、ZZZ は
> ReadOnly Property にしておいた方が良いのでは。
> これってどういうことですか?

別のクラスを返すプロパティを持った、階層構造なクラスの設計思想の話です。
たとえば、下記のような使い方を想定しているかどうか、という話。

 Dim tcc1 As New TextColorClass()
 Dim tcc2 As New TextColorClass()
 Dim tcc3 As New TextColorClass()
 tcc1.ZZZ.EEE = Color.Black '(1)これは普通の使い方
 tcc2.ZZZ = tcc1.ZZZ '(2)別のインスタンスを代入しても構わないか?
 tcc2.ZZZ.FFF = Color.Red '(3)tcc1.ZZZ.FFF も連動して Red になって良いのか?
 tcc3.ZZZ = Nothing '(4)Nothing の代入を許容するのか?
 'tcc3.ZZZ.FFF = Color.Red '(5)ZZZ が Nothing だと、これはエラーになる

利用する側から ZZZ を差し替える操作、つまり上記の 2 や 4 を行うことを
当初から想定して設計しているのであれば、
 Public Property ZZZ As TextColorClass2
のままで構いません。この場合は ReadOnly にする必要はありません。

しかし、もしもクラスで最初に New されている
>> Property ZZZ As New TextColorClass2
のインスタンスが変更されることを想定していないのであれば、
書き換えられるのは都合が悪いわけで、その場合は最初から
Public ReadOnly Property にしておいた方が望ましい、という判断です。



たとえば ListBox の場合、
 ListBox1.Items.Add(NewItem())
 ListBox2.Items.Add(NewItem())
のように書くことはできますが、
 ListBox1.Items = NewCollection()
 ListBox2.Items = Nothing
のような書き方はできないようになっていますよね。これも同様の話で、
Items プロパティが、あえて ReadOnly に設計されている、ということです。

あるいは SplitContainer をフォームに貼って使うような場合…
SplitContainer の Panel1 は ReadOnly なプロパティなので、
 SplitContainer1.Panel1 = Nothing
 SplitContainer2.Panel1 = Me.Panel3
のようにして、別のパネルに差し替えることはできないように設計されています。
しかし、既がにセットされている Panel1 のプロパティを操作することはできるので、
 SplitContainer1.Panel1.BackColor = Color.Yellow
のようにして、パネル片側の背景色を黄色に塗ることは可能です。
今回の ZZZ がそうであるように。

引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ