2014-11-18T01:50:51の更新内容
programming/netfx/property/index.wiki.txt
current | previous | |
---|---|---|
1,1665 | 0,0 | |
+ |
${smdncms:title,プロパティ} |
|
+ |
${smdncms:keywords,プロパティ,自動実装,初期値,アクセサ,インデクサ,例外,INotifyPropertyChanged} |
|
+ |
${smdncms:document_versions,codelang=cs,codelang=vb} |
|
+ | ||
+ |
ここでは.NET Frameworkにおけるプロパティと、プロパティの実装に関する事項・注意点などについて解説します。 またインデクサやインデックス付きプロパティについても解説します。 |
|
+ | ||
+ |
#adunit |
|
+ | ||
+ |
-関連するページ |
|
+ |
--[[programming/vb.net/property]] (VB) |
|
+ |
--[[programming/netfx/classlibrary/1_interoperability]] |
|
+ |
--[[programming/netfx/valuetype_referencetype#value_type_property]] |
|
+ |
--[[programming/netfx/collections/2_generic_1_list#modify_valuetype_element]] |
|
+ |
--[[programming/netfx/reflection]] |
|
+ |
--[[programming/netfx/struct/3_arrayfields]] |
|
+ |
--[[programming/netfx/attributes]] |
|
+ |
--[[programming/netfx/exceptions]] |
|
+ |
---[[programming/netfx/exceptions/9_2_netfx4_types]] |
|
+ |
---[[programming/netfx/exceptions/9_3_netfx4_messages]] |
|
+ | ||
+ | ||
+ |
*プロパティ |
|
+ |
C#やVBではプロパティ構文がサポートされています。 プロパティはクラス・構造体・インターフェイスに持たせることができます。 見かけ上はプロパティに対する値の取得・設定はフィールドに対するものと変わりありません。 |
|
+ | ||
+ |
#tabpage(codelang=cs,container-title=クラスとプロパティ) |
|
+ |
#code{{ |
|
+ |
using System; |
|
+ | ||
+ |
class Account { |
|
+ |
// プロパティ |
|
+ |
public int ID { |
|
+ |
get { |
|
+ |
return _id; |
|
+ |
} |
|
+ |
set { |
|
+ |
_id = value; |
|
+ |
} |
|
+ |
} |
|
+ | ||
+ |
// プロパティの値を保持するフィールド |
|
+ |
private int _id; |
|
+ |
} |
|
+ | ||
+ |
class Sample { |
|
+ |
public static void Main() |
|
+ |
{ |
|
+ |
var a = new Account(); |
|
+ | ||
+ |
// プロパティに値を設定する |
|
+ |
a.ID = 3; |
|
+ | ||
+ |
// プロパティから値を取得する |
|
+ |
int id = a.ID; |
|
+ | ||
+ |
Console.WriteLine(id); |
|
+ |
} |
|
+ |
} |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code{{ |
|
+ |
Imports System |
|
+ | ||
+ |
Class Account |
|
+ |
' プロパティ |
|
+ |
Public Property ID As Integer |
|
+ |
Get |
|
+ |
Return _id |
|
+ |
End Get |
|
+ |
Set |
|
+ |
_id = value |
|
+ |
End Set |
|
+ |
End Property |
|
+ | ||
+ |
' プロパティの値を保持するフィールド |
|
+ |
Private _id As Integer |
|
+ |
End Class |
|
+ | ||
+ |
Class Sample |
|
+ |
Public Shared Sub Main() |
|
+ |
Dim a As New Account() |
|
+ | ||
+ |
' プロパティに値を設定する |
|
+ |
a.ID = 3 |
|
+ | ||
+ |
' プロパティから値を取得する |
|
+ |
Dim id As Integer = a.ID |
|
+ | ||
+ |
Console.WriteLine(id) |
|
+ |
End Sub |
|
+ |
End Class |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ |
#prompt(実行結果){{ |
|
+ |
3 |
|
+ |
}} |
|
+ | ||
+ |
プロパティではこのように[[アクセサメソッド>#accessor_methods]]を使ってフィールドに対する値の取得・設定を行います。 |
|
+ | ||
+ |
実装を持たないインターフェイスでは、プロパティを以下のように記述します。 |
|
+ | ||
+ |
#tabpage(codelang=cs,container-title=インターフェイスとプロパティ) |
|
+ |
#code{{ |
|
+ |
interface IAccount { |
|
+ |
// プロパティ |
|
+ |
int ID { get; set; } |
|
+ |
} |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code{{ |
|
+ |
Interface IAccount |
|
+ |
' プロパティ |
|
+ |
Property ID As Integer |
|
+ |
End Interface |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ |
**アクセサメソッド [#accessor_methods] |
|
+ |
.NET Frameworkにおけるプロパティは、``set``アクセサまたは/および``get``アクセサの''アクセサメソッド''の組み合わせとなっています。 プロパティを記述するコードはコンパイル時にアクセサメソッドとして展開され、またプロパティに対するアクセスはアクセサメソッドの呼び出しに展開されます。 |
|
+ | ||
+ |
次の例のように、プロパティを持つ型を[[リフレクション>programming/netfx/reflection]]によって調べると、プロパティとなるメンバとは別にアクセサメソッドが存在していることが確認できます。 |
|
+ | ||
+ |
#tabpage(codelang=cs,container-title=リフレクションによってアクセサメソッドを確認する) |
|
+ |
#code{{ |
|
+ |
using System; |
|
+ |
using System.Reflection; |
|
+ | ||
+ |
class Account { |
|
+ |
public int ID { |
|
+ |
get { |
|
+ |
return _id; |
|
+ |
} |
|
+ |
set { |
|
+ |
_id = value; |
|
+ |
} |
|
+ |
} |
|
+ | ||
+ |
private int _id; |
|
+ |
} |
|
+ | ||
+ |
class Sample { |
|
+ |
public static void Main() |
|
+ |
{ |
|
+ |
// 型に含まれるすべてのパブリックなインスタンスメンバを表示する |
|
+ |
foreach (var m in typeof(Account).GetMembers(BindingFlags.Public | BindingFlags.Instance)) { |
|
+ |
Console.WriteLine("{0}\t{1}", m.MemberType, m); |
|
+ |
} |
|
+ |
} |
|
+ |
} |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code{{ |
|
+ |
Imports System |
|
+ |
Imports System.Reflection |
|
+ | ||
+ |
Class Account |
|
+ |
Public Property ID As Integer |
|
+ |
Get |
|
+ |
Return _id |
|
+ |
End Get |
|
+ |
Set |
|
+ |
_id = value |
|
+ |
End Set |
|
+ |
End Property |
|
+ | ||
+ |
Private _id As Integer |
|
+ |
End Class |
|
+ | ||
+ |
Class Sample |
|
+ |
Public Shared Sub Main() |
|
+ |
' 型に含まれるすべてのパブリックなインスタンスメンバを表示する |
|
+ |
For Each m As MemberInfo In GetType(Account).GetMembers(BindingFlags.Public Or BindingFlags.Instance) |
|
+ |
Console.WriteLine("{0}{1}{2}", m.MemberType, vbTab, m) |
|
+ |
Next |
|
+ |
End Sub |
|
+ |
End Class |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ |
#prompt(実行結果){{ |
|
+ |
Method Int32 get_ID() |
|
+ |
Method Void set_ID(Int32) |
|
+ |
Method Boolean Equals(System.Object) |
|
+ |
Method Int32 GetHashCode() |
|
+ |
Method System.Type GetType() |
|
+ |
Method System.String ToString() |
|
+ |
Constructor Void .ctor() |
|
+ |
Property Int32 ID |
|
+ |
}} |
|
+ | ||
+ |
この結果にある``get_ID``がプロパティ``ID``に対応する``get``アクセサ、``set_ID``が``set``アクセサとなります。 |
|
+ | ||
+ |
このようにして作成されるアクセサメソッドを直接呼び出すことはできません。 そのようなコードを記述した場合はコンパイルエラーとなります。 |
|
+ | ||
+ |
#tabpage(codelang=cs,container-title=アクセサメソッドの呼び出し) |
|
+ |
#code{{ |
|
+ |
using System; |
|
+ | ||
+ |
class Account { |
|
+ |
public int ID { |
|
+ |
get { |
|
+ |
return _id; |
|
+ |
} |
|
+ |
set { |
|
+ |
_id = value; |
|
+ |
} |
|
+ |
} |
|
+ | ||
+ |
private int _id; |
|
+ |
} |
|
+ | ||
+ |
class Sample { |
|
+ |
public static void Main() |
|
+ |
{ |
|
+ |
var a = new Account(); |
|
+ | ||
+ |
// error CS0571: `Account.ID.set': 演算子またはアクセサーを明示的に呼び出すことはできません。 |
|
+ |
a.set_ID(3); |
|
+ | ||
+ |
// error CS0571: `Account.ID.get': 演算子またはアクセサーを明示的に呼び出すことはできません。 |
|
+ |
int id = a.get_ID(); |
|
+ | ||
+ |
Console.WriteLine(id); |
|
+ |
} |
|
+ |
} |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code{{ |
|
+ |
Imports System |
|
+ | ||
+ |
Class Account |
|
+ |
Public Property ID As Integer |
|
+ |
Get |
|
+ |
Return _id |
|
+ |
End Get |
|
+ |
Set |
|
+ |
_id = value |
|
+ |
End Set |
|
+ |
End Property |
|
+ | ||
+ |
Private _id As Integer |
|
+ |
End Class |
|
+ | ||
+ |
Class Sample |
|
+ |
Public Shared Sub Main() |
|
+ |
Dim a As New Account() |
|
+ | ||
+ |
' error BC30456: 'set_ID' は 'Account' のメンバではありません。 |
|
+ |
a.set_ID(3) |
|
+ | ||
+ |
' error BC30456: 'set_ID' は 'Account' のメンバではありません。 |
|
+ |
Dim id As Integer = a.get_ID() |
|
+ | ||
+ |
Console.WriteLine(id) |
|
+ |
End Sub |
|
+ |
End Class |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ |
また、アクセサメソッドと同じシグネチャのメソッドが存在する場合もコンパイルエラーとなります。 逆に、シグネチャが異なっていればアクセサメソッドと同名のメソッドを作成することはできます。 |
|
+ | ||
+ |
#tabpage(codelang=cs,container-title=アクセサメソッドと同名のメソッド) |
|
+ |
#code{{ |
|
+ |
using System; |
|
+ | ||
+ |
class Account { |
|
+ |
// error CS0082: 型 'Account' は、'Account.get_ID()' と呼ばれるメンバを同じパラメータの型で既に予約しています。 |
|
+ |
// error CS0082: 型 'Account' は、'Account.set_ID(int)' と呼ばれるメンバを同じパラメータの型で既に予約しています。 |
|
+ |
public int ID { |
|
+ |
get { |
|
+ |
return _id; |
|
+ |
} |
|
+ |
set { |
|
+ |
_id = value; |
|
+ |
} |
|
+ |
} |
|
+ | ||
+ |
private int _id; |
|
+ | ||
+ |
public int get_ID() |
|
+ |
{ |
|
+ |
return 0; |
|
+ |
} |
|
+ | ||
+ |
public void set_ID(int val) |
|
+ |
{ |
|
+ |
} |
|
+ | ||
+ |
// このメソッドはアクセサメソッドとシグネチャが異なるため |
|
+ |
// コンパイルエラーとはならない |
|
+ |
public void set_ID(string arg) |
|
+ |
{ |
|
+ |
} |
|
+ |
} |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code{{ |
|
+ |
Imports System |
|
+ | ||
+ |
Class Account |
|
+ |
' error BC31060: property 'ID' は、同じ名前のメンバーと class 'Account' で競合する 'get_ID' を暗黙的に定義しています。 |
|
+ |
' error BC31060: property 'ID' は、同じ名前のメンバーと class 'Account' で競合する 'set_ID' を暗黙的に定義しています。 |
|
+ |
Public Property ID As Integer |
|
+ |
Get |
|
+ |
Return _id |
|
+ |
End Get |
|
+ |
Set |
|
+ |
_id = value |
|
+ |
End Set |
|
+ |
End Property |
|
+ | ||
+ |
Private _id As Integer |
|
+ | ||
+ |
Public Function get_ID() As Integer |
|
+ |
Return 0 |
|
+ |
End Function |
|
+ | ||
+ |
Public Sub set_ID(ByVal val As Integer) |
|
+ |
End Sub |
|
+ | ||
+ |
' このメソッドはアクセサメソッドとシグネチャが異なるため |
|
+ |
' コンパイルエラーとはならない |
|
+ |
Public Sub set_ID(ByVal arg As String) |
|
+ |
End Sub |
|
+ |
End Class |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ | ||
+ |
**読み取り専用・書き込み専用・アクセシビリティ |
|
+ |
.NET Frameworkにおけるプロパティでは、プロパティへのアクセスを読み取り専用(または書き込み専用)にすることができます。 |
|
+ | ||
+ |
#tabpage(codelang=cs,container-title=読み取り専用プロパティ) |
|
+ |
#code{{ |
|
+ |
using System; |
|
+ | ||
+ |
class Account { |
|
+ |
// getアクセサのみのプロパティ(読み取り専用) |
|
+ |
public int ID { |
|
+ |
get { |
|
+ |
return _id; |
|
+ |
} |
|
+ |
} |
|
+ | ||
+ |
private int _id; |
|
+ | ||
+ |
public Account(int id) |
|
+ |
{ |
|
+ |
// コンストラクタでプロパティの初期値を設定する |
|
+ |
this._id = id; |
|
+ |
} |
|
+ |
} |
|
+ | ||
+ |
class Sample { |
|
+ |
public static void Main() |
|
+ |
{ |
|
+ |
var a = new Account(3); |
|
+ | ||
+ |
// 読み取り専用プロパティで値を設定することはできない |
|
+ |
// error CS0200: プロパティまたはインデクサー 'Account.ID' は読み取り専用なので、割り当てることはできません。 |
|
+ |
a.ID = 42; |
|
+ |
} |
|
+ |
} |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code{{ |
|
+ |
Imports System |
|
+ | ||
+ |
Class Account |
|
+ |
' getアクセサのみのプロパティ(読み取り専用) |
|
+ |
Public ReadOnly Property ID As Integer |
|
+ |
Get |
|
+ |
Return _id |
|
+ |
End Get |
|
+ |
End Property |
|
+ | ||
+ |
Private _id As Integer |
|
+ | ||
+ |
Public Sub New(ByVal id As Integer) |
|
+ |
' コンストラクタでプロパティの初期値を設定する |
|
+ |
Me._id = id |
|
+ |
End Sub |
|
+ |
End Class |
|
+ | ||
+ |
Class Sample |
|
+ |
Public Shared Sub Main() |
|
+ |
Dim a As New Account(3) |
|
+ | ||
+ |
' 読み取り専用プロパティで値を設定することはできない |
|
+ |
' error BC30526: プロパティ 'ID' は 'ReadOnly' です。 |
|
+ |
a.ID = 42 |
|
+ |
End Sub |
|
+ |
End Class |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ |
派生クラスのみに公開する場合などを除けば、書き込み専用プロパティを外部に公開することはまれです。 こういった場合はプロパティではなく``SetXXX``といったメソッドを提供するほうが自然です。 |
|
+ | ||
+ |
#tabpage(codelang=cs) |
|
+ |
#code{{ |
|
+ |
using System; |
|
+ | ||
+ |
class Account { |
|
+ |
/* |
|
+ |
* 書き込み専用プロパティ |
|
+ |
public int ID { |
|
+ |
set { |
|
+ |
_id = value; |
|
+ |
} |
|
+ |
} |
|
+ |
*/ |
|
+ | ||
+ |
// フィールドに値を設定するメソッド |
|
+ |
public void SetID(int newid) |
|
+ |
{ |
|
+ |
_id = newid; |
|
+ |
} |
|
+ | ||
+ |
private int _id; |
|
+ |
} |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code{{ |
|
+ |
Imports System |
|
+ | ||
+ |
Class Account |
|
+ |
' 書き込み専用プロパティ |
|
+ |
'Public WriteOnly Property ID As Integer |
|
+ |
' Set |
|
+ |
' _id = value |
|
+ |
' End Set |
|
+ |
'End Property |
|
+ | ||
+ |
' フィールドに値を設定するメソッド |
|
+ |
Public Sub SetID(ByVal newid As Integer) |
|
+ |
_id = newid |
|
+ |
End Sub |
|
+ | ||
+ |
Private _id As Integer |
|
+ |
End Class |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ |
また、アクセサメソッドのアクセシビリティも``set``と``get``で異なるものを指定することができるため、例えば派生クラスからのみ設定可能なプロパティを作成するといったことができます。 |
|
+ | ||
+ |
#tabpage(codelang=cs,container-title=アクセサごとに異なるアクセシビリティ) |
|
+ |
#code{{ |
|
+ |
using System; |
|
+ | ||
+ |
class Account { |
|
+ |
public int ID { |
|
+ |
// getアクセサはpublic |
|
+ |
get { |
|
+ |
return _id; |
|
+ |
} |
|
+ |
// setアクセサはprotected |
|
+ |
// (派生クラスからのみsetできる) |
|
+ |
protected set { |
|
+ |
_id = value; |
|
+ |
} |
|
+ |
} |
|
+ | ||
+ |
private int _id; |
|
+ |
} |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code{{ |
|
+ |
Imports System |
|
+ | ||
+ |
Class Account |
|
+ |
Public Property ID As Integer |
|
+ |
' GetアクセサはPublic |
|
+ |
Get |
|
+ |
Return _id |
|
+ |
End Get |
|
+ |
' SetアクセサはProtected |
|
+ |
' (派生クラスからのみsetできる) |
|
+ |
Protected Set |
|
+ |
_id = value |
|
+ |
End Set |
|
+ |
End Property |
|
+ | ||
+ |
Private _id As Integer |
|
+ |
End Class |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ | ||
+ | ||
+ |
*自動実装 |
|
+ |
C#やVBでは構文によるサポートによって''プロパティの自動実装''を行うことができます。 これは、プロパティのアクセサ部分で行う処理が単純に値を返すだけ/設定するだけとなる場合に、その記述を省略することができるものです。 |
|
+ | ||
+ |
#tabpage(codelang=cs,container-title=プロパティの自動実装) |
|
+ |
#code{{ |
|
+ |
using System; |
|
+ | ||
+ |
class Account { |
|
+ |
// プロパティの自動実装 |
|
+ |
public int ID { |
|
+ |
get; |
|
+ |
set; |
|
+ |
} |
|
+ | ||
+ |
// 自動実装したプロパティは次のようなコードに展開される |
|
+ |
/* |
|
+ |
public int ID { |
|
+ |
get { return _id; } |
|
+ |
set { _id = value; } |
|
+ |
} |
|
+ | ||
+ |
private int _id; |
|
+ |
*/ |
|
+ |
} |
|
+ | ||
+ |
class Sample { |
|
+ |
public static void Main() |
|
+ |
{ |
|
+ |
var a = new Account(); |
|
+ | ||
+ |
a.ID = 3; |
|
+ | ||
+ |
int id = a.ID; |
|
+ | ||
+ |
Console.WriteLine(id); |
|
+ |
} |
|
+ |
} |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code{{ |
|
+ |
Imports System |
|
+ | ||
+ |
Class Account |
|
+ |
' プロパティの自動実装 |
|
+ |
Public Property ID As Integer |
|
+ | ||
+ |
' 自動実装したプロパティは次のようなコードに展開される |
|
+ |
'Public Property ID As Integer |
|
+ |
' Get |
|
+ |
' Return _id |
|
+ |
' End Get |
|
+ |
' Set |
|
+ |
' _id = value |
|
+ |
' End Set |
|
+ |
'End Property |
|
+ |
' |
|
+ |
'Private _id As Integer |
|
+ |
End Class |
|
+ | ||
+ |
Class Sample |
|
+ |
Public Shared Sub Main() |
|
+ |
Dim a As New Account() |
|
+ | ||
+ |
a.ID = 3 |
|
+ | ||
+ |
Dim id As Integer = a.ID |
|
+ | ||
+ |
Console.WriteLine(id) |
|
+ |
End Sub |
|
+ |
End Class |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ |
#prompt(実行結果){{ |
|
+ |
3 |
|
+ |
}} |
|
+ | ||
+ |
プロパティの自動実装はC# 3.0、VB2010以降でサポートされています。 |
|
+ | ||
+ |
**バッキングフィールド |
|
+ |
プロパティの自動実装を行う場合、プロパティの値を保持するフィールド(''バッキングフィールド'')も自動的に作成されます。 このフィールドはリフレクションによって調べることができます。 |
|
+ | ||
+ |
#tabpage(codelang=cs,container-title=バッキングフィールド名の表示) |
|
+ |
#code{{ |
|
+ |
using System; |
|
+ |
using System.Reflection; |
|
+ | ||
+ |
class Account { |
|
+ |
public int ID { |
|
+ |
get; |
|
+ |
set; |
|
+ |
} |
|
+ |
} |
|
+ | ||
+ |
class Sample { |
|
+ |
public static void Main() |
|
+ |
{ |
|
+ |
foreach (var f in typeof(Account).GetFields(BindingFlags.NonPublic | BindingFlags.Instance)) { |
|
+ |
Console.WriteLine(f); |
|
+ |
} |
|
+ |
} |
|
+ |
} |
|
+ |
}} |
|
+ | ||
+ |
#prompt(実行結果){{ |
|
+ |
Int32 <ID>k__BackingField |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code{{ |
|
+ |
Imports System |
|
+ |
Imports System.Reflection |
|
+ | ||
+ |
Class Account |
|
+ |
Public Property ID As Integer |
|
+ |
End Class |
|
+ | ||
+ |
Class Sample |
|
+ |
Public Shared Sub Main() |
|
+ |
For Each f As FieldInfo In GetType(Account).GetFields(BindingFlags.NonPublic Or BindingFlags.Instance) |
|
+ |
Console.WriteLine(f) |
|
+ |
Next |
|
+ |
End Sub |
|
+ |
End Class |
|
+ |
}} |
|
+ | ||
+ |
#prompt(実行結果){{ |
|
+ |
Int32 _ID |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ |
このようにC#とVBでは生成されるバッキングフィールド名が異なり、C#では``<&var{プロパティ名};>k__BackingField``、VBでは``_&var{プロパティ名};``となるようです。 |
|
+ | ||
+ |
**自動実装プロパティの初期値 |
|
+ |
C# 6.0以降、VB2010以降では自動実装プロパティに初期値を与えることができます。 |
|
+ | ||
+ |
#tabpage(codelang=cs,container-title=自動実装プロパティの初期値) |
|
+ |
#code(cs,C# 6.0){{ |
|
+ |
class Account { |
|
+ |
public int ID { get; set } = 3; |
|
+ |
} |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code(vb,VB2010){{ |
|
+ |
Class Account |
|
+ |
Public Property ID As Integer = 3 |
|
+ |
End Class |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ |
C#では読み取り専用かつ初期値を持たせたプロパティを自動実装することができますが、VBではできません。 |
|
+ | ||
+ |
#code(cs,C# 6.0){{ |
|
+ |
class Account { |
|
+ |
public int ID { get; } = 3; |
|
+ |
} |
|
+ |
}} |
|
+ | ||
+ |
読み取り専用かつ初期値を持たせたプロパティの自動実装は、インスタンス作成時に値を指定して以降は一切値を変更できないような''不変オブジェクト''を作成する上で非常に役立ちます。 |
|
+ | ||
+ |
このようなプロパティの自動実装を使えない場合、バッキングフィールド・アクセサメソッド・コンストラクタでの初期値の設定などをすべて記述することで不変オブジェクトを実装することができます。 |
|
+ | ||
+ |
#tabpage(codelang=cs,container-title=不変オブジェクト) |
|
+ |
#code{{ |
|
+ |
class Account { |
|
+ |
// 読み取り専用プロパティ |
|
+ |
public int ID { |
|
+ |
get { return _id; } |
|
+ |
} |
|
+ | ||
+ |
// 読み取り専用フィールド |
|
+ |
private readonly int _id; |
|
+ | ||
+ |
public Account(int id) |
|
+ |
{ |
|
+ |
// フィールドの値をコンストラクタで設定する |
|
+ |
_id = id; |
|
+ |
} |
|
+ |
} |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code{{ |
|
+ |
Class Account |
|
+ |
' 読み取り専用プロパティ |
|
+ |
Public ReadOnly Property ID As Integer |
|
+ |
Get |
|
+ |
Return _id |
|
+ |
End Get |
|
+ |
End Property |
|
+ | ||
+ |
' 読み取り専用フィールド |
|
+ |
Private ReadOnly _id As Integer |
|
+ | ||
+ |
Public Sub New(ByVal id As Integer) |
|
+ |
' フィールドの値をコンストラクタで設定する |
|
+ |
_id = id |
|
+ |
End Sub |
|
+ |
End Class |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ | ||
+ |
*インデクサ |
|
+ |
''インデクサ''(indexer)とは添字(index)をつけることができるプロパティで、添字を使ってインスタンスを配列のように扱えるようにするものです。 プロパティ名を省略して直接インスタンスに添字を指定して値の取得/設定を行うように見えるため、VBでは''既定のプロパティ''とも呼ばれます。 インデクサは[[string型>programming/netfx/string/1_operations]]や[[List>programming/netfx/collections/2_generic_1_list]], [[Dictionary>programming/netfx/collections/2_generic_2_dictionary]]などの[[コレクションクラス>programming/netfx/collections]]で使われています。 |
|
+ | ||
+ |
#tabpage(codelang=cs,container-title=string型とインデクサ) |
|
+ |
#code{{ |
|
+ |
using System; |
|
+ | ||
+ |
class Sample { |
|
+ |
public static void Main() |
|
+ |
{ |
|
+ |
string str = "Hello, world!"; |
|
+ | ||
+ |
// インデクサによって文字列インスタンスをcharの配列のように扱える |
|
+ |
Console.WriteLine(str[0]); |
|
+ |
Console.WriteLine(str[1]); |
|
+ |
Console.WriteLine(str[2]); |
|
+ |
Console.WriteLine(str[3]); |
|
+ |
Console.WriteLine(str[4]); |
|
+ |
} |
|
+ |
} |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code{{ |
|
+ |
Imports System |
|
+ | ||
+ |
Class Sample |
|
+ |
Public Shared Sub Main() |
|
+ |
Dim str As String = "Hello, world!" |
|
+ | ||
+ |
' インデクサによって文字列インスタンスをCharの配列のように扱える |
|
+ |
Console.WriteLine(str(0)) |
|
+ |
Console.WriteLine(str(1)) |
|
+ |
Console.WriteLine(str(2)) |
|
+ |
Console.WriteLine(str(3)) |
|
+ |
Console.WriteLine(str(4)) |
|
+ |
End Sub |
|
+ |
End Class |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ |
#prompt(実行結果){{ |
|
+ |
H |
|
+ |
e |
|
+ |
l |
|
+ |
l |
|
+ |
o |
|
+ |
}} |
|
+ | ||
+ |
配列とは異なり、インデクサの添字部分には整数型以外の型も指定することができます。 例えば[[Dictionary<TKey, TValue>>programming/netfx/collections/2_generic_2_dictionary]]では任意の型を添字(''キー'')として使用することができ、インデクサではこのキーを指定することによって対応する値にアクセスすることが出来るようになっています。 |
|
+ | ||
+ |
#tabpage(codelang=cs,container-title=Dictionaryとインデクサ) |
|
+ |
#code{{ |
|
+ |
using System; |
|
+ |
using System.Collections.Generic; |
|
+ | ||
+ |
class Sample { |
|
+ |
public static void Main() |
|
+ |
{ |
|
+ |
// 文字列を添字(キー)として使用するDictionary |
|
+ |
var dict = new Dictionary<string, int>(); |
|
+ | ||
+ |
dict["Alice"] = 0; |
|
+ |
dict["Bob"] = 1; |
|
+ |
dict["Charlie"] = 2; |
|
+ |
} |
|
+ |
} |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code{{ |
|
+ |
Imports System |
|
+ |
Imports System.Collections.Generic |
|
+ | ||
+ |
Class Sample |
|
+ |
Public Shared Sub Main() |
|
+ |
' 文字列を添字(キー)として使用するDictionary |
|
+ |
Dim dict As New Dictionary(Of String, Integer)() |
|
+ | ||
+ |
dict("Alice") = 0 |
|
+ |
dict("Bob") = 1 |
|
+ |
dict("Charlie") = 2 |
|
+ |
End Sub |
|
+ |
End Class |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ |
実装上はインデクサもプロパティの1形態となっています。 実際、リフレクションでもインデクサはPropertyInfoとして扱われます。 ([[programming/netfx/reflection#PropertyInfo.GetValue]]) |
|
+ | ||
+ |
型にインデクサを実装する場合は、次のようにします。 |
|
+ | ||
+ |
#tabpage(codelang=cs,container-title=インデクサの実装) |
|
+ |
#code{{ |
|
+ |
using System; |
|
+ | ||
+ |
class ByteArray { |
|
+ |
private byte[] arr; |
|
+ | ||
+ |
public ByteArray(int length) |
|
+ |
{ |
|
+ |
arr = new byte[length]; |
|
+ |
} |
|
+ | ||
+ |
// インデクサ |
|
+ |
public byte this[int index] { |
|
+ |
get { return arr[index]; } |
|
+ |
set { arr[index] = value; } |
|
+ |
} |
|
+ |
} |
|
+ | ||
+ |
class Sample { |
|
+ |
public static void Main() |
|
+ |
{ |
|
+ |
var arr = new ByteArray(3); |
|
+ | ||
+ |
arr[0] = 2; |
|
+ |
arr[1] = 3; |
|
+ |
arr[2] = 4; |
|
+ | ||
+ |
for (var i = 0; i < 3; i++) { |
|
+ |
Console.WriteLine(arr[i]); |
|
+ |
} |
|
+ |
} |
|
+ |
} |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code{{ |
|
+ |
Imports System |
|
+ | ||
+ |
Class ByteArray |
|
+ |
Private arr() As Byte |
|
+ | ||
+ |
Public Sub New(ByVal length As Integer) |
|
+ |
arr = New Byte(length - 1) {} |
|
+ |
End Sub |
|
+ | ||
+ |
' インデクサ |
|
+ |
Public Default Property Item(ByVal index As Integer) As Byte |
|
+ |
Get |
|
+ |
Return arr(index) |
|
+ |
End Get |
|
+ |
Set |
|
+ |
arr(index) = value |
|
+ |
End Set |
|
+ |
End Property |
|
+ |
End Class |
|
+ | ||
+ |
Class Sample |
|
+ |
Public Shared Sub Main() |
|
+ |
Dim arr As New ByteArray(3) |
|
+ | ||
+ |
arr(0) = 2 |
|
+ |
arr(1) = 3 |
|
+ |
arr(2) = 4 |
|
+ | ||
+ |
For i As Integer = 0 To 2 |
|
+ |
Console.WriteLine(arr(i)) |
|
+ |
Next |
|
+ |
End Sub |
|
+ |
End Class |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ |
#prompt(実行結果){{ |
|
+ |
2 |
|
+ |
3 |
|
+ |
4 |
|
+ |
}} |
|
+ | ||
+ | ||
+ |
**インデックス付きプロパティとインデクサの名前 |
|
+ |
VBではプロパティに添字をもたせることで''インデックス付きプロパティ''を作成することができます。 また、``Default``修飾子によってインデックス付きプロパティを''既定のプロパティ''とすることによって、プロパティをインデクサとすることができます。 |
|
+ | ||
+ |
#code(vb,既定のプロパティとインデックス付きプロパティ){{ |
|
+ |
Imports System |
|
+ | ||
+ |
Class C |
|
+ |
' インデクサ(既定のプロパティ) |
|
+ |
Public Default Property Indexer(ByVal index As Integer) As String |
|
+ |
' 実装は省略 |
|
+ |
Get |
|
+ |
Return Nothing |
|
+ |
End Get |
|
+ |
Set |
|
+ |
End Set |
|
+ |
End Property |
|
+ | ||
+ |
' インデックスつきプロパティ |
|
+ |
Public Property IndexedProperty(ByVal index As Integer) As String |
|
+ |
' 実装は省略 |
|
+ |
Get |
|
+ |
Return Nothing |
|
+ |
End Get |
|
+ |
Set |
|
+ |
End Set |
|
+ |
End Property |
|
+ |
End Class |
|
+ | ||
+ |
Class Sample |
|
+ |
Public Shared Sub Main() |
|
+ |
Dim c As New C() |
|
+ | ||
+ |
' インデクサに値を設定する |
|
+ |
c(0) = "foo" |
|
+ |
c(1) = "bar" |
|
+ |
c(2) = "baz" |
|
+ | ||
+ |
' 既定のプロパティではプロパティ名を省略することができる |
|
+ |
' (つまり上記のコードは以下と同じ) |
|
+ |
c.Indexer(0) = "foo" |
|
+ |
c.Indexer(1) = "bar" |
|
+ |
c.Indexer(2) = "baz" |
|
+ | ||
+ |
' インデックス付きプロパティに値を設定する |
|
+ |
c.IndexedProperty(0) = "foo" |
|
+ |
c.IndexedProperty(1) = "bar" |
|
+ |
c.IndexedProperty(2) = "baz" |
|
+ |
End Sub |
|
+ |
End Class |
|
+ |
}} |
|
+ | ||
+ |
一方C#ではインデックス付きプロパティを作ることはできません。 そのため、かわりに[[配列やIList<T>などのコレクションを返すプロパティとして実装する>#collection_property]]必要があります。 また、任意の名前でインデクサを作成することもできず、型で定義できるインデクサの数もひとつに限られます。 |
|
+ | ||
+ |
C#ではインデクサに名前を指定できないため、C#で作成したインデクサを他の言語からインデックス付きプロパティとして参照する場合はデフォルトの名前である``Item``を使用します。 この名前を変更するには、インデクサに属性&msdn(netfx,type,System.Runtime.CompilerServices.IndexerNameAttribute){IndexerNameAttribute};を指定します。 |
|
+ | ||
+ |
#code(cs,インデックス付きプロパティとして参照される場合の名前を指定する例){{ |
|
+ |
using System; |
|
+ | ||
+ |
class C { |
|
+ |
// Indexerという名前のインデックス付きプロパティとしてアクセスできるようにする |
|
+ |
[System.Runtime.CompilerServices.IndexerName("Indexer")] |
|
+ |
public string this[int index] { |
|
+ |
// 実装は省略 |
|
+ |
get { return null; } |
|
+ |
set { } |
|
+ |
} |
|
+ |
} |
|
+ |
}} |
|
+ | ||
+ |
インデクサが他の言語からアクセスされることを考慮する場合、インデクサには適切な名前を付けておくことが推奨されます。 (関連:[[programming/netfx/classlibrary/1_interoperability]]) |
|
+ | ||
+ | ||
+ |
*コレクションを返すプロパティ [#collection_property] |
|
+ |
インデクサはコレクションやそれに類する機能を持つクラスで実装すべきもので、多くの場合はインデクサよりも単に配列や&msdn(netfx,type,System.Collections.Generic.List`1){List<T>};などのコレクション、&msdn(netfx,type,System.Collections.Generic.IList`1){IList<T>};や&msdn(netfx,type,System.Collections.Generic.ICollection`1){ICollection<T>};などのインターフェイスを返すプロパティを用意するほうが適切です。 特にList<T>やIList<T>を返すプロパティはインデックス付きプロパティの代替として使用することができます。 |
|
+ | ||
+ |
インデックス付きプロパティのような機能をコレクションを返すプロパティとして公開する場合は、値を格納するコレクション自体を変更されないように読み取り専用で公開します。 |
|
+ | ||
+ |
#tabpage(codelang=cs,container-title=コレクションを返すプロパティ) |
|
+ |
#code{{ |
|
+ |
using System; |
|
+ |
using System.Collections.Generic; |
|
+ | ||
+ |
class Account { |
|
+ |
private readonly List<string> _addresses = new List<string>(); |
|
+ | ||
+ |
public List<string> Addresses { |
|
+ |
get { return _addresses; } |
|
+ |
} |
|
+ |
} |
|
+ | ||
+ |
class Sample { |
|
+ |
public static void Main() |
|
+ |
{ |
|
+ |
var a = new Account(); |
|
+ | ||
+ |
a.Addresses.Add("alice@example.com"); |
|
+ |
a.Addresses.Add("alice-2@mail.example.net"); |
|
+ | ||
+ |
a.Addresses[0] = "alice-1@mail.example.net"; |
|
+ | ||
+ |
// 読み取り専用なのでコレクション自体を置き換える操作はできない |
|
+ |
//a.Addresses = new List<string>() {"alice@example.com"}; |
|
+ |
} |
|
+ |
} |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code{{ |
|
+ |
Imports System |
|
+ |
Imports System.Collections.Generic |
|
+ | ||
+ |
Class Account |
|
+ |
Private ReadOnly _addresses As List(Of String) = New List(Of String)() |
|
+ | ||
+ |
Public ReadOnly Property Addresses As List(Of String) |
|
+ |
Get |
|
+ |
Return _addresses |
|
+ |
End Get |
|
+ |
End Property |
|
+ |
End Class |
|
+ | ||
+ |
Class Sample |
|
+ |
Public Shared Sub Main() |
|
+ |
Dim a As New Account() |
|
+ | ||
+ |
a.Addresses.Add("alice@example.com") |
|
+ |
a.Addresses.Add("alice-2@mail.example.net") |
|
+ | ||
+ |
a.Addresses(0) = "alice-1@mail.example.net" |
|
+ | ||
+ |
' 読み取り専用なのでコレクション自体を置き換える操作はできない |
|
+ |
'a.Addresses = New List(Of String)() {"alice@example.com"} |
|
+ |
End Sub |
|
+ |
End Class |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ |
さらに、公開されるコレクション自体も参照専用としたい(コレクションの内容を変更させたくない)場合は、&msdn(netfx,type,System.Collections.Generic.IReadOnlyList`1){IReadOnlyListインターフェイス};(.NET Framework 4.5以降)や&msdn(netfx,type,System.Collections.ObjectModel.ReadOnlyCollection`1){ReadOnlyCollection};として公開する方法をとることができます。 |
|
+ | ||
+ |
#tabpage(codelang=cs,container-title=参照専用のコレクションを返すプロパティ) |
|
+ |
#code{{ |
|
+ |
using System; |
|
+ |
using System.Collections.Generic; |
|
+ | ||
+ |
class Account { |
|
+ |
public Account(IEnumerable<string> addresses) |
|
+ |
{ |
|
+ |
_addresses = new List<string>(addresses); |
|
+ |
} |
|
+ | ||
+ |
private readonly List<string> _addresses; |
|
+ | ||
+ |
// IReadOnlyListとして公開する |
|
+ |
public IReadOnlyList<string> Addresses { |
|
+ |
get { return _addresses; } |
|
+ |
} |
|
+ |
} |
|
+ | ||
+ |
class Sample { |
|
+ |
public static void Main() |
|
+ |
{ |
|
+ |
var a = new Account(new[] {"alice@example.com", "alice-2@mail.example.net"}); |
|
+ | ||
+ |
// コレクションの参照 |
|
+ |
Console.WriteLine(a.Addresses.Count); |
|
+ |
Console.WriteLine(a.Addresses[0]); |
|
+ | ||
+ |
// IReadOnlyListではインデクサに対する設定はサポートされない |
|
+ |
//a.Addresses[0] = "alice-1@mail.example.net"; |
|
+ | ||
+ |
// またAddなどコレクションの変更を行うメソッドも用意されない |
|
+ |
//a.Addresses.Add("alice-1@mail.example.net"); |
|
+ |
} |
|
+ |
} |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code{{ |
|
+ |
Imports System |
|
+ |
Imports System.Collections.Generic |
|
+ | ||
+ |
Class Account |
|
+ |
Public Sub New(ByVal addresses As IEnumerable(Of String)) |
|
+ |
_addresses = New List(Of String)(addresses) |
|
+ |
End Sub |
|
+ | ||
+ |
Private ReadOnly _addresses As List(Of String) |
|
+ | ||
+ |
' IReadOnlyListとして公開する |
|
+ |
Public ReadOnly Property Addresses As IReadOnlyList(Of String) |
|
+ |
Get |
|
+ |
Return _addresses |
|
+ |
End Get |
|
+ |
End Property |
|
+ |
End Class |
|
+ | ||
+ |
Class Sample |
|
+ |
Public Shared Sub Main() |
|
+ |
Dim a As New Account(New String() {"alice@example.com", "alice-2@mail.example.net"}) |
|
+ | ||
+ |
' コレクションの参照 |
|
+ |
Console.WriteLine(a.Addresses.Count) |
|
+ |
Console.WriteLine(a.Addresses(0)) |
|
+ | ||
+ |
' IReadOnlyListではインデクサに対する設定はサポートされない |
|
+ |
'a.Addresses(0) = "alice-1@mail.example.net" |
|
+ | ||
+ |
' またAddなどコレクションの変更を行うメソッドも用意されない |
|
+ |
'a.Addresses.Add("alice-1@mail.example.net") |
|
+ |
End Sub |
|
+ |
End Class |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ |
その他コレクションクラスおよびインターフェイスについては[[programming/netfx/collections/0_abstract]]、読み取り専用コレクションについては[[programming/netfx/collections/3_objectmodel_1_collection#ReadOnlyCollection]]を参照してください。 |
|
+ | ||
+ | ||
+ |
**イテレータ |
|
+ |
プロパティにおいてもイテレータ構文を使用することができます。 これにより、IEnumerableを返すプロパティを簡単に記述することが出来ます。 |
|
+ | ||
+ |
#tabpage(codelang=cs,container-title=イテレータを返すプロパティ) |
|
+ |
#code{{ |
|
+ |
using System; |
|
+ |
using System.Collections.Generic; |
|
+ | ||
+ |
class C { |
|
+ |
public IEnumerable<int> P { |
|
+ |
get { |
|
+ |
yield return 0; |
|
+ |
yield return 1; |
|
+ |
yield return 2; |
|
+ |
yield return 3; |
|
+ |
yield return 4; |
|
+ |
} |
|
+ |
} |
|
+ |
} |
|
+ | ||
+ |
class Sample { |
|
+ |
public static void Main() |
|
+ |
{ |
|
+ |
var c = new C(); |
|
+ | ||
+ |
// プロパティPから列挙される値を表示する |
|
+ |
foreach (var val in c.P) { |
|
+ |
Console.WriteLine(val); |
|
+ |
} |
|
+ |
} |
|
+ |
} |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code{{ |
|
+ |
Imports System |
|
+ |
Imports System.Collections.Generic |
|
+ | ||
+ |
Class C |
|
+ |
Public ReadOnly Iterator Property P As IEnumerable(Of Integer) |
|
+ |
Get |
|
+ |
Yield 0 |
|
+ |
Yield 1 |
|
+ |
Yield 2 |
|
+ |
Yield 3 |
|
+ |
Yield 4 |
|
+ |
End Get |
|
+ |
End Property |
|
+ |
End Class |
|
+ | ||
+ |
Class Sample |
|
+ |
Public Shared Sub Main() |
|
+ |
Dim c As New C() |
|
+ | ||
+ |
' プロパティPから列挙される値を表示する |
|
+ |
For Each val As Integer In c.P |
|
+ |
Console.WriteLine(val) |
|
+ |
Next |
|
+ |
End Sub |
|
+ |
End Class |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ |
#prompt(実行結果){{ |
|
+ |
0 |
|
+ |
1 |
|
+ |
2 |
|
+ |
3 |
|
+ |
4 |
|
+ |
}} |
|
+ | ||
+ |
イテレータに関しては[[programming/netfx/enumerator/2_iterator]]を参照してください。 |
|
+ | ||
+ | ||
+ |
*プロパティと例外 |
|
+ |
プロパティではアクセサメソッドを使ってフィールドの値を取得・設定するため、その際に値の検証を行う処理を記述することができます。 また、検証した結果として例外をスローすることもできます。 例えば、設定される値がプロパティとして有効な値の範囲外だった場合には&msdn(netfx,type,System.ArgumentOutOfRangeException){ArgumentOutOfRangeException};、``null``/``Nothing``を許容しない場合には&msdn(netfx,type,System.ArgumentNullException){ArgumentNullException};、その他不正な値であれば&msdn(netfx,type,System.ArgumentException){ArgumentException};をスローすることができます。 |
|
+ | ||
+ |
これらの例外をスローする場合は、例外コンストラクタの引数で例外メッセージを記述するとともに、引数&var{paramName};に原因となったプロパティの名前を設定します。 また、ArgumentOutOfRangeExceptionでは引数&var{actualValue};に原因となった値を指定することができ、これによりエラー原因が把握しやすくなります。 |
|
+ | ||
+ |
#tabpage(codelang=cs,container-title=プロパティで値を検証してArgumentExceptionをスローする例) |
|
+ |
#code{{ |
|
+ |
using System; |
|
+ | ||
+ |
// 角度を表すクラス |
|
+ |
class Degree { |
|
+ |
public int Value { |
|
+ |
get { return val; } |
|
+ |
set |
|
+ |
{ |
|
+ |
// プロパティに設定される値を検証する |
|
+ |
if (value < 0 || 360 <= value) |
|
+ |
throw new ArgumentOutOfRangeException("Value", value, "角度には0以上360未満の値を指定してください。"); |
|
+ | ||
+ |
// 検証した結果問題ない値ならフィールドに値を保持する |
|
+ |
val = value; |
|
+ |
} |
|
+ |
} |
|
+ | ||
+ |
private int val; |
|
+ |
} |
|
+ | ||
+ |
class Sample { |
|
+ |
public static void Main() |
|
+ |
{ |
|
+ |
var d = new Degree(); |
|
+ | ||
+ |
d.Value = 360; |
|
+ |
} |
|
+ |
} |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code{{ |
|
+ |
Imports System |
|
+ | ||
+ |
' 角度を表すクラス |
|
+ |
Class Degree |
|
+ |
Public Property Value As Integer |
|
+ |
Get |
|
+ |
Return val |
|
+ |
End Get |
|
+ |
Set |
|
+ |
' プロパティに設定される値を検証する |
|
+ |
If value < 0 OrElse 360 <= value Then |
|
+ |
Throw New ArgumentOutOfRangeException("Value", value, "角度には0以上360未満の値を指定してください。") |
|
+ |
End If |
|
+ | ||
+ |
' 検証した結果問題ない値ならフィールドに値を保持する |
|
+ |
val = value |
|
+ |
End Set |
|
+ |
End Property |
|
+ | ||
+ |
Private val As Integer |
|
+ |
End Class |
|
+ | ||
+ |
Class Sample |
|
+ |
Public Shared Sub Main() |
|
+ |
Dim d As New Degree() |
|
+ | ||
+ |
d.Value = 360 |
|
+ |
End Sub |
|
+ |
End Class |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ |
#prompt(実行結果){{ |
|
+ |
ハンドルされていない例外: System.ArgumentOutOfRangeException: 角度には0以上360未満の値を指定してください。 |
|
+ |
パラメーター名:Value |
|
+ |
実際の値は 360 です。 |
|
+ |
場所 Degree.set_Value(Int32 value) |
|
+ |
場所 Sample.Main() |
|
+ |
}} |
|
+ | ||
+ |
一方この例の場合では、例外をスローせず、次のように値を適正な範囲に丸め込む実装とすることも考えられます。 |
|
+ | ||
+ |
#tabpage(codelang=cs,container-title=プロパティに設定された値を正規化する例) |
|
+ |
#code{{ |
|
+ |
using System; |
|
+ | ||
+ |
// 角度を表すクラス |
|
+ |
class Degree { |
|
+ |
public int Value { |
|
+ |
get { return val; } |
|
+ |
set |
|
+ |
{ |
|
+ |
val = value; |
|
+ | ||
+ |
// 値を0 <= val < 360の範囲に正規化する |
|
+ |
for (;;) { |
|
+ |
if (val < 0) |
|
+ |
val += 360; |
|
+ |
else if (360 <= val) |
|
+ |
val -= 360; |
|
+ |
else |
|
+ |
break; |
|
+ |
} |
|
+ |
} |
|
+ |
} |
|
+ | ||
+ |
private int val; |
|
+ |
} |
|
+ | ||
+ |
class Sample { |
|
+ |
public static void Main() |
|
+ |
{ |
|
+ |
var d = new Degree(); |
|
+ | ||
+ |
d.Value = 480; |
|
+ | ||
+ |
Console.WriteLine(d.Value); |
|
+ |
} |
|
+ |
} |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code{{ |
|
+ |
Imports System |
|
+ | ||
+ |
' 角度を表すクラス |
|
+ |
Class Degree |
|
+ |
Public Property Value As Integer |
|
+ |
Get |
|
+ |
Return val |
|
+ |
End Get |
|
+ |
Set |
|
+ |
val = value |
|
+ | ||
+ |
' 値を0 <= val < 360の範囲に正規化する |
|
+ |
Do |
|
+ |
If val < 0 Then |
|
+ |
val += 360 |
|
+ |
Else If 360 <= val |
|
+ |
val -= 360 |
|
+ |
Else |
|
+ |
Exit Do |
|
+ |
End If |
|
+ |
Loop |
|
+ |
End Set |
|
+ |
End Property |
|
+ | ||
+ |
Private val As Integer |
|
+ |
End Class |
|
+ | ||
+ |
Class Sample |
|
+ |
Public Shared Sub Main() |
|
+ |
Dim d As New Degree() |
|
+ | ||
+ |
d.Value = 480 |
|
+ | ||
+ |
Console.WriteLine(d.Value) |
|
+ |
End Sub |
|
+ |
End Class |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ |
#prompt(実行結果){{ |
|
+ |
120 |
|
+ |
}} |
|
+ | ||
+ |
一般に、プロパティでは単に値の取得・設定のみを行うべきで、それ以上の''副作用''が起こることは避けるべきです。 例えば上記の例においては、設定した値とその後に取得される値が異なることから、実装を知らずに結果だけを見ると意図した動作と異なるような違和感を覚える場合もあります。 この他にも、プロパティを設定することがインスタンス内の他のメンバに影響するような実装(一つのプロパティで複数のフィールドを変更するなど)は避けるべきです。 |
|
+ | ||
+ |
また例外に関しても、プロパティから以下に挙げるようなもの以外の例外をスローする場合にはメソッドとして実装したほうがよいとされます。 プロパティからスローされることが想定(あるいは許容)される例外と状況の主なものとしては次のようなものがあります。 |
|
+ | ||
+ |
:&msdn(netfx,type,System.ArgumentOutOfRangeException){ArgumentOutOfRangeException};, &msdn(netfx,type,System.ArgumentNullException){ArgumentNullException};, &msdn(netfx,type,System.ArgumentException){ArgumentException};|プロパティに設定される値としては不正な場合 |
|
+ |
:&msdn(netfx,type,System.ComponentModel.InvalidEnumArgumentException){InvalidEnumArgumentException};|プロパティに設定される列挙体の値が不正な場合 |
|
+ |
:&msdn(netfx,type,System.IndexOutOfRangeException){IndexOutOfRangeException};|インデクサに指定されるインデックスが範囲内の場合の場合 |
|
+ |
:&msdn(netfx,type,System.InvalidOperationException){InvalidOperationException};|現在のインスタンスの状態ではプロパティの表す機能を要求できない場合 (例えば、処理の進行中にその処理に影響するプロパティを変更しようとするなど) |
|
+ |
:&msdn(netfx,type,System.ObjectDisposedException){ObjectDisposedException};|インスタンスが破棄された後にプロパティにアクセスしようとした場合 (詳細:[[programming/netfx/disposing]]) |
|
+ |
:&msdn(netfx,type,System.NotSupportedException){NotSupportedException};|インスタンスがプロパティの表す機能をサポートしていない場合 (例えば、読み取り専用として作成したインスタンスに対するプロパティの設定など) |
|
+ |
:&msdn(netfx,type,System.NotImplementedException){NotImplementedException};|プロパティの機能が未実装の場合 |
|
+ | ||
+ |
これ以外の例外をスローする必要がある場合は、プロパティよりメソッドとして公開するほうが望ましいかもしれません。 |
|
+ | ||
+ |
-関連:[[programming/netfx/exceptions/9_2_netfx4_types]] |
|
+ | ||
+ | ||
+ |
*プロパティ変更の通知 (INotifyPropertyChanged) [#INotifyPropertyChanged] |
|
+ |
プロパティに対する変更をインスタンス外に通知する汎用的な手段として、.NET Frameworkでは&msdn(netfx,type,System.ComponentModel.INotifyPropertyChanged){INotifyPropertyChangedインターフェイス};が用意されています。 これはデータバインディングなどの目的でプロパティの変更を通知したい場合に使用するもので、データソースとなるインスタンスでプロパティが変更された場合に&msdn(netfx,member,System.ComponentModel.INotifyPropertyChanged.PropertyChanged){PropertyChangedイベント};を発生させ、データの表示を行う''ビュー''などに変更が行われたことを通知することができます。 |
|
+ | ||
+ |
#tabpage(codelang=cs,container-title=INotifyPropertyChangedの実装と使用例) |
|
+ |
#code{{ |
|
+ |
using System; |
|
+ |
using System.ComponentModel; |
|
+ |
using System.Reflection; |
|
+ | ||
+ |
class Account : INotifyPropertyChanged { |
|
+ |
// プロパティが変更された場合に発行するイベント |
|
+ |
public event PropertyChangedEventHandler PropertyChanged; |
|
+ | ||
+ |
// 変更を通知するプロパティ |
|
+ |
private int _id; |
|
+ | ||
+ |
public int ID { |
|
+ |
get { return _id; } |
|
+ |
set { |
|
+ |
if (value != _id) { |
|
+ |
// 値が変更された場合、フィールドの値を更新したのちイベントを発行する |
|
+ |
_id = value; |
|
+ |
RaisePropertyChanged("ID"); |
|
+ |
} |
|
+ |
} |
|
+ |
} |
|
+ | ||
+ |
private string _name; |
|
+ | ||
+ |
public string Name { |
|
+ |
get { return _name; } |
|
+ |
set { |
|
+ |
if (!string.Equals(value, _name)) { |
|
+ |
_name = value; |
|
+ |
RaisePropertyChanged("Name"); |
|
+ |
} |
|
+ |
} |
|
+ |
} |
|
+ | ||
+ |
// プロパティが変更された場合にPropertyChangedイベントを発行するメソッド |
|
+ |
private void RaisePropertyChanged(string propertyName) |
|
+ |
{ |
|
+ |
if (PropertyChanged != null) |
|
+ |
PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); |
|
+ |
} |
|
+ |
} |
|
+ | ||
+ |
class Sample { |
|
+ |
static void Main() |
|
+ |
{ |
|
+ |
var a = new Account(); |
|
+ | ||
+ |
// プロパティの変更を購読するイベントハンドラを割り当てる |
|
+ |
a.PropertyChanged += PropertyChanged; |
|
+ | ||
+ |
// プロパティの値を変更する |
|
+ |
a.ID = 3; |
|
+ |
a.Name = "Alice"; |
|
+ |
} |
|
+ | ||
+ |
private static void PropertyChanged(object sender, PropertyChangedEventArgs e) |
|
+ |
{ |
|
+ |
// 変更されたプロパティの値をリフレクションによって取得する |
|
+ |
object newValue = sender.GetType().GetProperty(e.PropertyName).GetValue(sender, null); |
|
+ | ||
+ |
Console.WriteLine("プロパティ'{0}'の値が'{1}'に変更されました", e.PropertyName, newValue); |
|
+ |
} |
|
+ |
} |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code{{ |
|
+ |
Imports System |
|
+ |
Imports System.ComponentModel |
|
+ |
Imports System.Reflection |
|
+ | ||
+ |
Class Account |
|
+ |
Implements INotifyPropertyChanged |
|
+ | ||
+ |
' プロパティが変更された場合に発行するイベント |
|
+ |
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged |
|
+ | ||
+ |
' 変更を通知するプロパティ |
|
+ |
Private _id As Integer |
|
+ | ||
+ |
Public Property ID As Integer |
|
+ |
Get |
|
+ |
Return _id |
|
+ |
End Get |
|
+ |
Set |
|
+ |
If value <> _id Then |
|
+ |
' 値が変更された場合、フィールドの値を更新したのちイベントを発行する |
|
+ |
_id = value |
|
+ |
RaisePropertyChanged("ID") |
|
+ |
End If |
|
+ |
End Set |
|
+ |
End Property |
|
+ | ||
+ |
Private _name As String |
|
+ | ||
+ |
Public Property Name As String |
|
+ |
Get |
|
+ |
Return _name |
|
+ |
End Get |
|
+ |
Set |
|
+ |
If Not String.Equals(value, _name) Then |
|
+ |
_name = value |
|
+ |
RaisePropertyChanged("Name") |
|
+ |
End If |
|
+ |
End Set |
|
+ |
End Property |
|
+ | ||
+ |
' プロパティが変更された場合にPropertyChangedイベントを発行するメソッド |
|
+ |
Private Sub RaisePropertyChanged(ByVal propertyName As String) |
|
+ |
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName)) |
|
+ |
End Sub |
|
+ |
End Class |
|
+ | ||
+ |
Class Sample |
|
+ |
Public Shared Sub Main() |
|
+ |
Dim a As New Account() |
|
+ | ||
+ |
' プロパティの変更を購読するイベントハンドラを割り当てる |
|
+ |
AddHandler a.PropertyChanged, AddressOf PropertyChanged |
|
+ | ||
+ |
' プロパティの値を変更する |
|
+ |
a.ID = 3 |
|
+ |
a.Name = "Alice" |
|
+ |
End Sub |
|
+ | ||
+ |
Private Shared Sub PropertyChanged(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) |
|
+ |
' 変更されたプロパティの値をリフレクションによって取得する |
|
+ |
Dim newValue As Object = sender.GetType().GetProperty(e.PropertyName).GetValue(sender, Nothing) |
|
+ | ||
+ |
Console.WriteLine("プロパティ'{0}'の値が'{1}'に変更されました", e.PropertyName, newValue) |
|
+ |
End Sub |
|
+ |
End Class |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ |
#prompt(実行結果){{ |
|
+ |
プロパティ'ID'の値が'3'に変更されました |
|
+ |
プロパティ'Name'の値が'Alice'に変更されました |
|
+ |
}} |
|
+ | ||
+ |
(この例で使用しているリフレクションについては[[programming/netfx/reflection#PropertyInfo.GetValue]]、イベント機構については[[programming/netfx/delegate/1_events]]を参照してください) |
|
+ | ||
+ |
INotifyPropertyChanged.PropertyChangedイベントでは、変更があったプロパティ名をPropertyChangedEventArgsで文字列として通知します。 このため、INotifyPropertyChangedを実装したクラスでプロパティ名を変更することになった場合には、このプロパティ名となる文字列(上記の例におけるRaisePropertyChangedに渡す引数)も合わせて変更する必要があります。 コンパイラではこの変更が妥当かどうかを検知できないため、変更を行う際には注意を払う必要があります。 |
|
+ | ||
+ |
このような問題に対して、.NET Framework 4.5以降では&msdn(netfx,type,System.Runtime.CompilerServices.CallerMemberNameAttribute){CallerMemberNameAttribute};を使うことができます。 この属性は、呼び出し元のメンバ名をメソッドの引数に自動的に代入するもので、C/C++において行番号やファイル名をソース中に埋め込む``__LINE__``や``__FILE__``といったマクロに似た効果をもつものです。 この属性を使うことで、メソッドの呼び出し元ではプロパティ名を指定する必要がなくなり、プロパティ名を文字列で指定する手間と誤りの可能性を減らすことができます。 |
|
+ | ||
+ |
これを使うと上記のサンプルにおけるプロパティ名の指定箇所は次のように簡略化することができます。 |
|
+ | ||
+ |
#tabpage(codelang=cs,container-title=CallerMemberNameAttributeを使ってプロパティ名の指定を省略する例) |
|
+ |
#code{{ |
|
+ |
using System; |
|
+ |
using System.ComponentModel; |
|
+ |
using System.Runtime.CompilerServices; |
|
+ | ||
+ |
class Account : INotifyPropertyChanged { |
|
+ |
public event PropertyChangedEventHandler PropertyChanged; |
|
+ | ||
+ |
private int _id; |
|
+ | ||
+ |
public int ID { |
|
+ |
get { return _id; } |
|
+ |
set { |
|
+ |
if (value != _id) { |
|
+ |
_id = value; |
|
+ |
RaisePropertyChanged(); // 呼び出し元はプロパティ名を指定する必要が無い |
|
+ |
} |
|
+ |
} |
|
+ |
} |
|
+ | ||
+ |
private string _name; |
|
+ | ||
+ |
public string Name { |
|
+ |
get { return _name; } |
|
+ |
set { |
|
+ |
if (!string.Equals(value, _name)) { |
|
+ |
_name = value; |
|
+ |
RaisePropertyChanged(); |
|
+ |
} |
|
+ |
} |
|
+ |
} |
|
+ | ||
+ |
// 引数propertyNameに呼び出し元のプロパティ名が代入された上で呼び出される |
|
+ |
private void RaisePropertyChanged([CallerMemberName] string propertyName = null) |
|
+ |
{ |
|
+ |
if (PropertyChanged != null) |
|
+ |
PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); |
|
+ |
} |
|
+ |
} |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code{{ |
|
+ |
Imports System |
|
+ |
Imports System.ComponentModel |
|
+ |
Imports System.Runtime.CompilerServices |
|
+ | ||
+ |
Class Account |
|
+ |
Implements INotifyPropertyChanged |
|
+ | ||
+ |
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged |
|
+ | ||
+ |
Private _id As Integer |
|
+ | ||
+ |
Public Property ID As Integer |
|
+ |
Get |
|
+ |
Return _id |
|
+ |
End Get |
|
+ |
Set |
|
+ |
If value <> _id Then |
|
+ |
_id = value |
|
+ |
RaisePropertyChanged() ' 呼び出し元はプロパティ名を指定する必要が無い |
|
+ |
End If |
|
+ |
End Set |
|
+ |
End Property |
|
+ | ||
+ |
Private _name As String |
|
+ | ||
+ |
Public Property Name As String |
|
+ |
Get |
|
+ |
Return _name |
|
+ |
End Get |
|
+ |
Set |
|
+ |
If Not String.Equals(value, _name) Then |
|
+ |
_name = value |
|
+ |
RaisePropertyChanged() |
|
+ |
End If |
|
+ |
End Set |
|
+ |
End Property |
|
+ | ||
+ |
' 引数propertyNameに呼び出し元のプロパティ名が代入された上で呼び出される |
|
+ |
Private Sub RaisePropertyChanged(<CallerMemberName> Optional ByVal propertyName As String = Nothing) |
|
+ |
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName)) |
|
+ |
End Sub |
|
+ |
End Class |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ |
CallerMemberNameAttributeを使うことによってプロパティの値の比較・設定・イベントの発行の一連の処理を共通化できるため、さらに次のように簡略化することができます。 |
|
+ | ||
+ |
#tabpage(codelang=cs,container-title=CallerMemberNameAttributeを使ってINotifyPropertyChangedの実装を簡略化した例) |
|
+ |
#code{{ |
|
+ |
using System; |
|
+ |
using System.Collections.Generic; |
|
+ |
using System.ComponentModel; |
|
+ |
using System.Runtime.CompilerServices; |
|
+ | ||
+ |
class Account : INotifyPropertyChanged { |
|
+ |
public event PropertyChangedEventHandler PropertyChanged; |
|
+ | ||
+ |
private int _id; |
|
+ | ||
+ |
public int ID { |
|
+ |
get { return _id; } |
|
+ |
set { SetValue(ref _id, value, EqualityComparer<int>.Default); } |
|
+ |
} |
|
+ | ||
+ |
private string _name; |
|
+ | ||
+ |
public string Name { |
|
+ |
get { return _name; } |
|
+ |
set { SetValue(ref _name, value, StringComparer.Ordinal); } |
|
+ |
} |
|
+ | ||
+ |
private void SetValue<T>(ref T storage, |
|
+ |
T newValue, |
|
+ |
IEqualityComparer<T> comparer, |
|
+ |
[CallerMemberName] string propertyName = null) |
|
+ |
{ |
|
+ |
// フィールドの現在の値と新しい値を比較する |
|
+ |
if (!comparer.Equals(storage, newValue)) { |
|
+ |
// もとのフィールドに新しい値を設定する |
|
+ |
storage = newValue; |
|
+ | ||
+ |
// イベントを発行する |
|
+ |
if (PropertyChanged != null) |
|
+ |
PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); |
|
+ |
} |
|
+ |
} |
|
+ |
} |
|
+ |
}} |
|
+ |
#tabpage(codelang=vb) |
|
+ |
#code{{ |
|
+ |
Imports System |
|
+ |
Imports System.Collections.Generic |
|
+ |
Imports System.ComponentModel |
|
+ |
Imports System.Runtime.CompilerServices |
|
+ | ||
+ |
Class Account |
|
+ |
Implements INotifyPropertyChanged |
|
+ | ||
+ |
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged |
|
+ | ||
+ |
Private _id As Integer |
|
+ | ||
+ |
Public Property ID As Integer |
|
+ |
Get |
|
+ |
Return _id |
|
+ |
End Get |
|
+ |
Set |
|
+ |
SetValue(_id, value, EqualityComparer(Of Integer).Default) |
|
+ |
End Set |
|
+ |
End Property |
|
+ | ||
+ |
Private _name As String |
|
+ | ||
+ |
Public Property Name As String |
|
+ |
Get |
|
+ |
Return _name |
|
+ |
End Get |
|
+ |
Set |
|
+ |
SetValue(_name, value, StringComparer.Ordinal) |
|
+ |
End Set |
|
+ |
End Property |
|
+ | ||
+ |
Private Sub SetValue(Of T)(ByRef storage As T, _ |
|
+ |
ByVal newValue As T, _ |
|
+ |
ByVal comparer As IEqualityComparer(Of T), _ |
|
+ |
<CallerMemberName> Optional ByVal propertyName As String = Nothing) |
|
+ |
' フィールドの現在の値と新しい値を比較する |
|
+ |
If Not comparer.Equals(storage, newValue) Then |
|
+ |
' もとのフィールドに新しい値を設定する |
|
+ |
storage = newValue |
|
+ | ||
+ |
' イベントを発行する |
|
+ |
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName)) |
|
+ |
End If |
|
+ |
End Sub |
|
+ |
End Class |
|
+ |
}} |
|
+ |
#tabpage-end |
|
+ | ||
+ |
この例で使用しているEqualityComparerおよびIEqualityComparerについては[[programming/netfx/comparison/1_equation]]を参照してください。 |
|
+ | ||
+ |
なお、[[ObservableCollectionクラス>programming/netfx/collections/3_objectmodel_2_observablecollection]]はINotifyPropertyChangedを実装しています。 コレクションへの通知を検知したい場合にはこのクラスを使うことができます。 |
|
+ | ||
+ |