web-dev-qa-db-ja.com

C#でのインデックス作成をサポートするプロパティの簡単な作成

C#では インデックス付きプロパティ が非常に便利だと思います。例えば:

_var myObj = new MyClass();
myObj[42] = "hello"; 
Console.WriteLine(myObj[42]);
_

ただし、私が知る限り、それ自体がインデックス作成をサポートするフィールドをサポートするためのシンタックスシュガーはありません(間違っている場合は修正してください)。例えば:

_var myObj = new MyClass();
myObj.field[42] = "hello"; 
Console.WriteLine(myObj.field[42]);
_

これが必要な理由は、クラスですでにindexプロパティを使用しているが、GetNumX()GetX()、およびSetX()関数が次のようになっているためです。

_public int NumTargetSlots {  
    get { return _Maker.NumRefs; }  
}
public ReferenceTarget GetTarget(int n) {
    return ReferenceTarget.Create(_Maker.GetReference(n));
}
public void SetTarget(int n, ReferenceTarget rt) {
    _Maker.ReplaceReference(n, rt._Target, true);
}
_

おそらくお分かりのように、これらを1つのインデックス可能なフィールドプロパティとして公開する方が理にかなっています。シンタックスシュガーが必要になるたびにこれを実現するカスタムクラスを作成できましたが、ボイラープレートコードはすべて不要のようです。

そこで、ボイラープレートをカプセル化し、インデックスを作成できるプロパティを簡単に作成できるようにするカスタムクラスを作成しました。このようにして、次のように新しいプロパティを追加できます。

_public IndexedProperty<ReferenceTarget> TargetArray  {
    get { 
       return new IndexedProperty<int, ReferenceTarget>(
           (int n) => GetTarget(n), 
           (int n, ReferenceTarget rt) => SetTarget(n, rt));
       }
}
_

この新しいIndexedPropertyクラスのコードは次のようになります。

_public class IndexedProperty<IndexT, ValueT>
{
    Action<IndexT, ValueT> setAction;
    Func<IndexT, ValueT> getFunc;

    public IndexedProperty(Func<IndexT, ValueT> getFunc, Action<IndexT, ValueT> setAction)
    {
        this.getFunc = getFunc;
        this.setAction = setAction;
    }

    public ValueT this[IndexT i]
    {
        get {
            return getFunc(i);
        }
        set {
            setAction(i, value);
        }
    }
}
_

だから私の質問は:これをすべて行うためのより良い方法はありますか

具体的には、C#でインデックス可能なフィールドプロパティを作成するためのより慣用的な方法はありますか?そうでない場合は、IndexedPropertyクラスをどのように改善できますか?

EDIT:さらなる調査の後、Jon Skeetはこれを " named indexer "と呼びます。

29
cdiggins

さて、最も簡単なのは、プロパティにIListを実装するオブジェクトを返すようにすることです。

IListを実装しているからといって、それ自体がコレクション自体であるという意味ではなく、特定のメソッドを実装しているということを覚えておいてください。

8
James Curran

あなたのアイデアが役に立ったと思ったので、それを拡張しました。これはあなたの質問に完全に答えるのかはっきりしないので、技術的には適切な答えではないかもしれませんが、プロパティインデクサーを探してここに来た人には役立つかもしれないと思いました。

まず、get-onlyプロパティとset-onlyプロパティをサポートできる必要があるため、次のシナリオでコードを少し変更しました。

取得と設定(非常に小さな変更):

public class IndexedProperty<TIndex, TValue>
{
    readonly Action<TIndex, TValue> SetAction;
    readonly Func<TIndex, TValue> GetFunc;

    public IndexedProperty(Func<TIndex, TValue> getFunc, Action<TIndex, TValue> setAction)
    {
        this.GetFunc = getFunc;
        this.SetAction = setAction;
    }

    public TValue this[TIndex i]
    {
        get
        {
            return GetFunc(i);
        }
        set
        {
            SetAction(i, value);
        }
    }
}

取得のみ:

public class ReadOnlyIndexedProperty<TIndex, TValue>
{
    readonly Func<TIndex, TValue> GetFunc;

    public ReadOnlyIndexedProperty(Func<TIndex, TValue> getFunc)
    {
        this.GetFunc = getFunc;
    }

    public TValue this[TIndex i]
    {
        get
        {
            return GetFunc(i);
        }
    }
}

設定のみ:

public class WriteOnlyIndexedProperty<TIndex, TValue>
{
    readonly Action<TIndex, TValue> SetAction;

    public WriteOnlyIndexedProperty(Action<TIndex, TValue> setAction)
    {
        this.SetAction = setAction;
    }

    public TValue this[TIndex i]
    {
        set 
        {
            SetAction(i, value);
        }
    }
}

簡単な使用例を次に示します。私はコレクションから継承し、名前付きインデクサーを作成します。この例は、単純で実用的ではないことを目的としています。

public class ExampleCollection<T> : Collection<T>
{
    public IndexedProperty<int, T> ExampleProperty
    {
        get
        {
            return new IndexedProperty<int, T>(GetIndex, SetIndex);
        }
    }

    private T GetIndex(int index)
    {
        return this[index];
    }
    private void SetIndex(int index, T value)
    {
        this[index] = value;
    }
}

例の野生のコレクション

この急いで構築された単体テストは、プロジェクトでExampleCollectionを実行したときの外観を示しています。

[TestClass]
public class IndexPropertyTests
{
    [TestMethod]
    public void IndexPropertyTest()
    {
        var MyExample = new ExampleCollection<string>();
        MyExample.Add("a");
        MyExample.Add("b");

        Assert.IsTrue(MyExample.ExampleProperty[0] == "a");
        Assert.IsTrue(MyExample.ExampleProperty[1] == "b");

        MyExample.ExampleProperty[0] = "c";

        Assert.IsTrue(MyExample.ExampleProperty[0] == "c");

    }
}

最後に、get-onlyバージョンとset-onlyバージョンを使用する場合は、次のようになります。

    public ReadOnlyIndexedProperty<int, T> ExampleProperty
    {
        get
        {
            return new ReadOnlyIndexedProperty<int, T>(GetIndex);
        }
    }

または:

    public WriteOnlyIndexedProperty<int, T> ExampleProperty
    {
        get
        {
            return new WriteOnlyIndexedProperty<int, T>(SetIndex);
        }
    }

どちらの場合も、結果は、get-only/set-onlyプロパティの動作を期待したとおりに機能します。

11
Brian MacKay

私はあなたが投稿したデザインが道だと思いますが、インターフェースを定義するという違いが1つあります。

public interface IIndexed<IndexT, ValueT>
{
    ValueT this[IndexT i] { get; set; }
}

そして、一般的なケースでは、元の質問に入力したクラスを使用します(このインターフェイスを実装します)。

基本クラスライブラリが適切なインターフェイスを提供してくれればいいのですが、そうではありません。ここでIListを返すと、倒錯します。

7
quentin-starin

これはあなたの質問に答えませんが、CILがあなたが説明したようなプロパティの作成をサポートしていることに注意するのは興味深いです-いくつかの言語(例えば、F#)はあなたがそのような方法でそれらを定義することもできます。

C#の_this[]_インデクサーは、これらの1つの特定のインスタンスであり、アプリのビルド時にItemに名前が変更されます。 C#コンパイラはこれを読み取る方法しか知らないため、F#ライブラリにTargetという「名前付きインデクサー」を記述し、それをC#で使用しようとすると、プロパティにアクセスできる唯一の方法は... get_Target(int)およびvoid set_Target(int, ...)メソッドを介して。吸う。

4
Mark H

クラスにIListを継承させないのはなぜですか。インデックスを使用して、独自のプロパティを追加するだけです。追加および削除機能は引き続き使用できますが、それらを使用しないことは不正ではありません。加えて、あなたはそれらをさらに先に持っておくことが役に立つかもしれません。

リストと配列の詳細については、以下を確認してください。 配列とリスト<>のどちらを使用するのが良いですか?

編集:

MSDNには、参照したいインデックスプロパティに関する記事があります。面倒なだけで複雑に見えません。

http://msdn.Microsoft.com/en-us/library/aa288464(VS.71).aspx

別のAddメソッドを作成できる別のオプションがありますが、オブジェクトのタイプによっては、addメソッドが常に呼び出されるとは限りません。ここで説明:

C#でList <T>のAddメソッドをオーバーライドするにはどうすればよいですか?

編集2:最初の投稿と同様

クラスに非表示のリストオブジェクトを作成してから、データを取得するための独自のメソッドを作成してみませんか。この方法では、追加と削除は表示されず、リストには既にインデックスが付けられています。

また、「名前付きインデクサー」とは、row ["My_Column_Name"]に相当するものを探しているという意味です。そのプロパティを実装する基本的な方法を示しているように見えるので、役立つと思われるMSDNの記事があります。

http://msdn.Microsoft.com/en-us/library/146h6tk5.aspx

class Test
    {
        private List<T> index;
        public T this[string name]{ get; set; }
        public T this[int i]
        {
            get
            {
                return index[i];
            }
            set
            {
                index[i] = value;
            }
        }
    }
3
Gage

いくつかの調査を行った後、自分のニーズによりよく適合する少し異なるソリューションを思いつきました。例は少し作成されていますが、それを適応させるために必要なものに適合しています。

使用法:

MyClass MC = new MyClass();
int x = MC.IntProperty[5];

そしてそれを機能させるためのコード:

public class MyClass
{
    public readonly IntIndexing IntProperty;

    public MyClass()
    {
        IntProperty = new IntIndexing(this);
    }

    private int GetInt(int index)
    {
        switch (index)
        {
            case 1:
                return 56;
            case 2:
                return 47;
            case 3:
                return 88;
            case 4:
                return 12;
            case 5:
                return 32;
            default:
                return -1;
        }
    }

    public class IntIndexing
    {
        private MyClass MC;

        internal IntIndexing(MyClass mc)
        {
            MC = mc;
        }

        public int this[int index]
        {
            get { return MC.GetInt(index); }
        }
    }
}
2
damichab

ここでの返信で提案されている2番目の方法に示されているように、明示的に実装されたインターフェイスを試してください: C#の名前付きインデックスプロパティ

0
George Birbilis