静的プロパティのセットを持つクラスを作成する方法を探しています。実行時に、データベースからこのオブジェクトに他の動的プロパティを追加できるようにしたいと思います。また、これらのオブジェクトにソート機能とフィルタリング機能を追加したいと思います。
C#でこれを行うにはどうすればよいですか?
回答:
あなたは辞書を使うかもしれません、例えば
Dictionary<string,object> properties;
私は同様のことが行われるほとんどの場合、それはこのように行われると思います。
いずれの場合も、実行時にのみ作成され、コードで使用しないため、setおよびgetアクセサーを使用して「実際の」プロパティを作成しても何も得られません...
次に、フィルタリングとソートの可能な実装を示す例を示します(エラーチェックなし)。
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication1 {
class ObjectWithProperties {
Dictionary<string, object> properties = new Dictionary<string,object>();
public object this[string name] {
get {
if (properties.ContainsKey(name)){
return properties[name];
}
return null;
}
set {
properties[name] = value;
}
}
}
class Comparer<T> : IComparer<ObjectWithProperties> where T : IComparable {
string m_attributeName;
public Comparer(string attributeName){
m_attributeName = attributeName;
}
public int Compare(ObjectWithProperties x, ObjectWithProperties y) {
return ((T)x[m_attributeName]).CompareTo((T)y[m_attributeName]);
}
}
class Program {
static void Main(string[] args) {
// create some objects and fill a list
var obj1 = new ObjectWithProperties();
obj1["test"] = 100;
var obj2 = new ObjectWithProperties();
obj2["test"] = 200;
var obj3 = new ObjectWithProperties();
obj3["test"] = 150;
var objects = new List<ObjectWithProperties>(new ObjectWithProperties[]{ obj1, obj2, obj3 });
// filtering:
Console.WriteLine("Filtering:");
var filtered = from obj in objects
where (int)obj["test"] >= 150
select obj;
foreach (var obj in filtered){
Console.WriteLine(obj["test"]);
}
// sorting:
Console.WriteLine("Sorting:");
Comparer<int> c = new Comparer<int>("test");
objects.Sort(c);
foreach (var obj in objects) {
Console.WriteLine(obj["test"]);
}
}
}
}
あなたはデータバインディングの目的のためにこれを必要とする場合は、実装することで...カスタム記述モデルでこれを行うことができICustomTypeDescriptor、TypeDescriptionProviderおよび/またはTypeCoverter、あなたはあなた自身の作成することができPropertyDescriptor、実行時にインスタンスを。これはDataGridView、PropertyGridなどのコントロールがプロパティを表示するために使用するものです。
リストにバインドするには、必要があるだろうITypedListとIList。基本的な並べ替え:IBindingList; フィルタリングや高度なソートのために:IBindingListView。「新しい行」を完全にサポートするには(DataGridView):(ICancelAddNewphew!)。
でもそれは大変な仕事です。DataTable(私はそれが嫌いですが)同じことをする安価な方法です。データバインディングが必要ない場合は、ハッシュテーブルを使用してください;-p
ここに簡単な例があります-しかし、あなたはもっとたくさんのことができます...
「プロパティ」というハッシュテーブルを作成し、それにプロパティを追加します。
あなたが本当にやりたいことを本当にやりたいと思っているのかはわかりませんが、理由は私にはわかりません。
JITされた後、クラスにプロパティを追加することはできません。
あなたが得ることができる最も近いものは、Reflection.Emitで動的にサブタイプを作成し、既存のフィールドをコピーすることですが、オブジェクトへのすべての参照を自分で更新する必要があります。
また、コンパイル時にこれらのプロパティにアクセスすることもできません。
何かのようなもの:
public class Dynamic
{
public Dynamic Add<T>(string key, T value)
{
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("DynamicAssembly"), AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("Dynamic.dll");
TypeBuilder typeBuilder = moduleBuilder.DefineType(Guid.NewGuid().ToString());
typeBuilder.SetParent(this.GetType());
PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(key, PropertyAttributes.None, typeof(T), Type.EmptyTypes);
MethodBuilder getMethodBuilder = typeBuilder.DefineMethod("get_" + key, MethodAttributes.Public, CallingConventions.HasThis, typeof(T), Type.EmptyTypes);
ILGenerator getter = getMethodBuilder.GetILGenerator();
getter.Emit(OpCodes.Ldarg_0);
getter.Emit(OpCodes.Ldstr, key);
getter.Emit(OpCodes.Callvirt, typeof(Dynamic).GetMethod("Get", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(typeof(T)));
getter.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(getMethodBuilder);
Type type = typeBuilder.CreateType();
Dynamic child = (Dynamic)Activator.CreateInstance(type);
child.dictionary = this.dictionary;
dictionary.Add(key, value);
return child;
}
protected T Get<T>(string key)
{
return (T)dictionary[key];
}
private Dictionary<string, object> dictionary = new Dictionary<string,object>();
}
私はこのマシンにVSをインストールしていないので、大きなバグがあるかどうかを知らせてください(まあ...大きなパフォーマンスの問題以外は、私は仕様を書きませんでした!)
今、あなたはそれを使うことができます:
Dynamic d = new Dynamic();
d = d.Add("MyProperty", 42);
Console.WriteLine(d.GetType().GetProperty("MyProperty").GetValue(d, null));
レイトバインディングをサポートする言語(たとえば、VB.NET)の通常のプロパティのように使用することもできます。
ICustomTypeDescriptorインターフェイスとディクショナリを使用して、これを正確に行いました。
動的プロパティのICustomTypeDescriptorの実装:
最近、実行時に追加および削除できるプロパティをいくつでも持つことができるレコードオブジェクトにグリッドビューをバインドする必要がありました。これは、ユーザーが結果セットに新しい列を追加して、追加のデータセットを入力できるようにするためです。
これは、各データ「行」をディクショナリとして持つことで実現できます。キーはプロパティ名で、値は文字列または指定した行のプロパティの値を格納できるクラスです。もちろん、Dictionaryオブジェクトのリストを持つことはグリッドにバインドすることができません。これがICustomTypeDescriptorの出番です。
Dictionaryのラッパークラスを作成し、ICustomTypeDescriptorインターフェイスに準拠させることで、オブジェクトのプロパティを返す動作をオーバーライドできます。
以下のデータ「行」クラスの実装を見てください。
/// <summary>
/// Class to manage test result row data functions
/// </summary>
public class TestResultRowWrapper : Dictionary<string, TestResultValue>, ICustomTypeDescriptor
{
//- METHODS -----------------------------------------------------------------------------------------------------------------
#region Methods
/// <summary>
/// Gets the Attributes for the object
/// </summary>
AttributeCollection ICustomTypeDescriptor.GetAttributes()
{
return new AttributeCollection(null);
}
/// <summary>
/// Gets the Class name
/// </summary>
string ICustomTypeDescriptor.GetClassName()
{
return null;
}
/// <summary>
/// Gets the component Name
/// </summary>
string ICustomTypeDescriptor.GetComponentName()
{
return null;
}
/// <summary>
/// Gets the Type Converter
/// </summary>
TypeConverter ICustomTypeDescriptor.GetConverter()
{
return null;
}
/// <summary>
/// Gets the Default Event
/// </summary>
/// <returns></returns>
EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
{
return null;
}
/// <summary>
/// Gets the Default Property
/// </summary>
PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
{
return null;
}
/// <summary>
/// Gets the Editor
/// </summary>
object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
{
return null;
}
/// <summary>
/// Gets the Events
/// </summary>
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
{
return new EventDescriptorCollection(null);
}
/// <summary>
/// Gets the events
/// </summary>
EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
{
return new EventDescriptorCollection(null);
}
/// <summary>
/// Gets the properties
/// </summary>
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
{
List<propertydescriptor> properties = new List<propertydescriptor>();
//Add property descriptors for each entry in the dictionary
foreach (string key in this.Keys)
{
properties.Add(new TestResultPropertyDescriptor(key));
}
//Get properties also belonging to this class also
PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(this.GetType(), attributes);
foreach (PropertyDescriptor oPropertyDescriptor in pdc)
{
properties.Add(oPropertyDescriptor);
}
return new PropertyDescriptorCollection(properties.ToArray());
}
/// <summary>
/// gets the Properties
/// </summary>
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
return ((ICustomTypeDescriptor)this).GetProperties(null);
}
/// <summary>
/// Gets the property owner
/// </summary>
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
#endregion Methods
//---------------------------------------------------------------------------------------------------------------------------
}
注:GetPropertiesメソッドでは、パフォーマンスのために一度読み取ったPropertyDescriptorsをキャッシュできますが、実行時に列を追加および削除しているので、常に再構築する必要があります。
GetPropertiesメソッドで、ディクショナリエントリに追加されたプロパティ記述子がTestResultPropertyDescriptor型であることにも気づくでしょう。これは、プロパティの設定および取得方法を管理するカスタムプロパティ記述子クラスです。以下の実装を見てください。
/// <summary>
/// Property Descriptor for Test Result Row Wrapper
/// </summary>
public class TestResultPropertyDescriptor : PropertyDescriptor
{
//- PROPERTIES --------------------------------------------------------------------------------------------------------------
#region Properties
/// <summary>
/// Component Type
/// </summary>
public override Type ComponentType
{
get { return typeof(Dictionary<string, TestResultValue>); }
}
/// <summary>
/// Gets whether its read only
/// </summary>
public override bool IsReadOnly
{
get { return false; }
}
/// <summary>
/// Gets the Property Type
/// </summary>
public override Type PropertyType
{
get { return typeof(string); }
}
#endregion Properties
//- CONSTRUCTOR -------------------------------------------------------------------------------------------------------------
#region Constructor
/// <summary>
/// Constructor
/// </summary>
public TestResultPropertyDescriptor(string key)
: base(key, null)
{
}
#endregion Constructor
//- METHODS -----------------------------------------------------------------------------------------------------------------
#region Methods
/// <summary>
/// Can Reset Value
/// </summary>
public override bool CanResetValue(object component)
{
return true;
}
/// <summary>
/// Gets the Value
/// </summary>
public override object GetValue(object component)
{
return ((Dictionary<string, TestResultValue>)component)[base.Name].Value;
}
/// <summary>
/// Resets the Value
/// </summary>
public override void ResetValue(object component)
{
((Dictionary<string, TestResultValue>)component)[base.Name].Value = string.Empty;
}
/// <summary>
/// Sets the value
/// </summary>
public override void SetValue(object component, object value)
{
((Dictionary<string, TestResultValue>)component)[base.Name].Value = value.ToString();
}
/// <summary>
/// Gets whether the value should be serialized
/// </summary>
public override bool ShouldSerializeValue(object component)
{
return false;
}
#endregion Methods
//---------------------------------------------------------------------------------------------------------------------------
}
このクラスで確認する主なプロパティは、GetValueとSetValueです。ここでは、ディクショナリとしてキャストされているコンポーネントと、その内部のキーの値が設定または取得されていることがわかります。このクラスのディクショナリがRowラッパークラスのタイプと同じであることが重要です。そうでない場合、キャストは失敗します。記述子が作成されると、キー(プロパティ名)が渡され、辞書をクエリして正しい値を取得するために使用されます。
私のブログから:
WPFで使用されるDependencyObjectsを調べる必要があります。これらは、実行時にプロパティを割り当てることができる同様のパターンに従います。上記のように、これは最終的にハッシュテーブルの使用を指します。
もう1つ、CSLA.Netが役立ちます。コードは自由に利用でき、あなたが求めている原則\パターンのいくつかを使用しています。
また、並べ替えとフィルタリングを検討している場合は、何らかのグリッドを使用することになると思います。実装するのに便利なインターフェースはICustomTypeDescriptorです。これにより、オブジェクトが反映されたときに何が起こるかを効果的にオーバーライドできるため、リフレクターをオブジェクト自体の内部ハッシュテーブルにポイントできます。
orsogufoのコードの一部の代替として、私は最近、この同じ問題の辞書を自分で使ったので、ここに[]演算子があります。
public string this[string key]
{
get { return properties.ContainsKey(key) ? properties[key] : null; }
set
{
if (properties.ContainsKey(key))
{
properties[key] = value;
}
else
{
properties.Add(key, value);
}
}
}
この実装では[]=、辞書にまだ存在しない場合に使用すると、セッターは新しいキーと値のペアを追加します。
また、私にとってpropertiesは、IDictionaryコンストラクタではそれをに初期化しnew SortedDictionary<string, string>()ます。
record[name_column] = DBConvert.To<string>(r[name_column]);がどこであるかサービス側で値を設定していますrecord。クライアント側でこの値を取得するにはどうすればよいですか?