84

この種の質問に対するEricLippertの答えは、通常、「設計、実装、テスト、および文書化のコストに見合う価値がなかったため」のようなものです。

しかし、それでも、もっと良い説明が欲しいです...私は新しいC#4機能についてのこのブログ投稿を読んでいました、そしてCOM相互運用についてのセクションで、次の部分が私の注意を引きました:

ちなみに、このコードはもう1つの新機能を使用しています。インデックス付きプロパティ(範囲の後の角かっこを詳しく見てください)。ただし、この機能はCOM相互運用でのみ使用できます。C#4.0では独自のインデックス付きプロパティを作成できません

いいけどなんで ?C#でインデックス付きのプロパティを作成できないことはすでに知っていて後悔していましたが、この文でもう一度考えさせられました。私はそれを実装するいくつかの正当な理由を見ることができます:

  • CLRはそれをサポートしている(たとえば、パラメーターPropertyInfo.GetValueがあるindex)ので、C#でそれを利用できないのは残念です
  • 記事に示されているように、COM相互運用機能でサポートされています(動的ディスパッチを使用)
  • VB.NETで実装されています
  • thisインデクサーを作成すること、つまりオブジェクト自体にインデックスを適用することはすでに可能です。したがって、同じ構文を維持し、プロパティ名に置き換えるだけで、アイデアをプロパティに拡張することはおそらく大したことではありません。

それはそのようなことを書くことを可能にするでしょう:

public class Foo
{
    private string[] _values = new string[3];
    public string Values[int index]
    {
        get { return _values[index]; }
        set { _values[index] = value; }
    }
}

現在、私が知っている唯一の回避策はValuesCollection、インデクサーを実装する内部クラス(たとえば)を作成し、Valuesその内部クラスのインスタンスを返すようにプロパティを変更することです。

これは非常に簡単ですが、面倒です...おそらくコンパイラが私たちのためにそれを行うことができます!オプションは、インデクサーを実装する内部クラスを生成し、パブリックジェネリックインターフェイスを介して公開することです。

// interface defined in the namespace System
public interface IIndexer<TIndex, TValue>
{
    TValue this[TIndex index]  { get; set; }
}

public class Foo
{
    private string[] _values = new string[3];

    private class <>c__DisplayClass1 : IIndexer<int, string>
    {
        private Foo _foo;
        public <>c__DisplayClass1(Foo foo)
        {
            _foo = foo;
        }

        public string this[int index]
        {
            get { return _foo._values[index]; }
            set { _foo._values[index] = value; }
        }
    }

    private IIndexer<int, string> <>f__valuesIndexer;
    public IIndexer<int, string> Values
    {
        get
        {
            if (<>f__valuesIndexer == null)
                <>f__valuesIndexer = new <>c__DisplayClass1(this);
            return <>f__valuesIndexer;
        }
    }
}

しかしもちろん、その場合、プロパティは実際にはを返し、実際IIndexer<int, string>にはインデックス付きプロパティではありません...実際のCLRインデックス付きプロパティを生成する方がよいでしょう。

どう思いますか ?この機能をC#で表示しますか?そうでない場合、なぜですか?

4

9 に答える 9

125

C#4の設計方法は次のとおりです。

最初に、言語に追加することを考えることができるすべての可能な機能のリストを作成しました。

次に、機能を「これは悪い、絶対にやらない」、「これは素晴らしい、やらなければならない」、「これは良いが今回はやらない」にまとめました。

次に、「お奨め」機能の設計、実装、テスト、文書化、出荷、保守に必要な予算を調べたところ、予算を100%上回っていることを発見しました。

そこで、たくさんのものを「持っている」バケットから「持っているといい」バケットに移動しました。

インデックス付きのプロパティは、「お奨め」リストの一番上にあることはありませんでした。彼らは「いい」リストで非常に低く、「悪い考え」リストでいちゃつく。

優れた機能Xの設計、実装、テスト、文書化、または保守に費やす1分ごとに、すばらしい機能A、B、C、D、E、F、およびGに費やすことができない分です。可能な限り最高の機能を実行します。インデックス付きのプロパティは便利ですが、実際に実装するのに十分なほど優れているとは言えません。

于 2010-05-10T22:52:16.637 に答える
23

AC# インデクサー、インデックス付きプロパティです。これはItemデフォルトで名前が付けられ (VB などからそのように参照できます)、必要に応じてIndexerNameAttributeで変更できます。

具体的になぜそのように設計されたのかはわかりませんが、意図的な制限のようです。ただし、メンバー コレクションのインデックス可能なオブジェクトを返す、インデックスのないプロパティのアプローチを推奨するフレームワーク デザイン ガイドラインとは一致しています。つまり、「インデックス可能であること」は型の特性です。複数の方法で索引付けできる場合は、実際にはいくつかのタイプに分割する必要があります。

于 2010-05-10T22:43:57.450 に答える
16

すでにある程度はできており、オブジェクト指向の側面で考える必要があるため、インデックス付きプロパティを追加すると、言語にノイズが増えるだけです。そして、別のことを行うための別の方法。

class Foo
{
    public Values Values { ... }
}

class Values
{
    public string this[int index] { ... }    
}

foo.Values[0]

個人的には、10 通りの方法ではなく、1 つの方法だけを見たいと思っています。しかし、もちろんこれは主観的な意見です。

于 2010-05-10T22:38:24.367 に答える
9

私は以前はインデックス付きプロパティのアイデアを支持していましたが、それが恐ろしいあいまいさを追加し、実際には機能を阻害することに気付きまし。インデックス付きプロパティは、子コレクション インスタンスがないことを意味します。それは良いことでもあり悪いことでもあります。実装するのが簡単で、囲んでいる所有者クラスへの参照が必要ありません。ただし、その子コレクションを何にも渡すことができないことも意味します。おそらく毎回列挙する必要があります。それに foreach を実行することもできません。さらに悪いことに、インデックス付きプロパティを見ただけでは、それがそれなのかコレクション プロパティなのかわかりません。

アイデアは合理的ですが、それは柔軟性のなさと急激なぎこちなさにつながるだけです。

于 2012-08-24T18:01:13.757 に答える
6

きれいで簡潔なコードを書こうとすると、インデックス付きのプロパティがないことに非常にイライラします。インデックス付きプロパティは、インデックス付きのクラス参照を提供したり、個々のメソッドを提供したりするのとはまったく異なる意味合いを持っています。インデックス付きプロパティを実装する内部オブジェクトへのアクセスを提供することは、オブジェクト指向の重要なコンポーネントの 1 つであるカプセル化を壊すことが多いため、受け入れられるとさえ考えられていることは少し気がかりです。

私はこの問題によく遭遇しますが、今日また遭遇したので、実際のコード例を提供します。書かれているインターフェースとクラスは、大まかに関連する情報のコレクションであるアプリケーション構成を格納します。名前付きのスクリプト フラグメントを追加する必要がありましたが、名前のないクラス インデクサーを使用すると、スクリプト フラグメントは構成の一部にすぎないため、非常に間違ったコンテキストを意味していました。

C# でインデックス付きプロパティを使用できる場合は、次のコードを実装できます (構文は this[key] を PropertyName[key] に変更)。

public interface IConfig
{
    // Other configuration properties removed for examp[le

    /// <summary>
    /// Script fragments
    /// </summary>
    string Scripts[string name] { get; set; }
}

/// <summary>
/// Class to handle loading and saving the application's configuration.
/// </summary>
internal class Config : IConfig, IXmlConfig
{
  #region Application Configuraiton Settings

    // Other configuration properties removed for examp[le

    /// <summary>
    /// Script fragments
    /// </summary>
    public string Scripts[string name]
    {
        get
        {
            if (!string.IsNullOrWhiteSpace(name))
            {
                string script;
                if (_scripts.TryGetValue(name.Trim().ToLower(), out script))
                    return script;
            }
            return string.Empty;
        }
        set
        {
            if (!string.IsNullOrWhiteSpace(name))
            {
                _scripts[name.Trim().ToLower()] = value;
                OnAppConfigChanged();
            }
        }
    }
    private readonly Dictionary<string, string> _scripts = new Dictionary<string, string>();

  #endregion

    /// <summary>
    /// Clears configuration settings, but does not clear internal configuration meta-data.
    /// </summary>
    private void ClearConfig()
    {
        // Other properties removed for example
        _scripts.Clear();
    }

  #region IXmlConfig

    void IXmlConfig.XmlSaveTo(int configVersion, XElement appElement)
    {
        Debug.Assert(configVersion == 2);
        Debug.Assert(appElement != null);

        // Saving of other properties removed for example

        if (_scripts.Count > 0)
        {
            var scripts = new XElement("Scripts");
            foreach (var kvp in _scripts)
            {
                var scriptElement = new XElement(kvp.Key, kvp.Value);
                scripts.Add(scriptElement);
            }
            appElement.Add(scripts);
        }
    }

    void IXmlConfig.XmlLoadFrom(int configVersion, XElement appElement)
    {
        // Implementation simplified for example

        Debug.Assert(appElement != null);
        ClearConfig();
        if (configVersion == 2)
        {
            // Loading of other configuration properites removed for example

            var scripts = appElement.Element("Scripts");
            if (scripts != null)
                foreach (var script in scripts.Elements())
                    _scripts[script.Name.ToString()] = script.Value;
        }
        else
            throw new ApplicaitonException("Unknown configuration file version " + configVersion);
    }

  #endregion
}

残念ながら、インデックス付きのプロパティは実装されていないため、それらを格納するクラスを実装し、それへのアクセスを提供しました。このドメイン モデルの構成クラスの目的はすべての詳細をカプセル化することであるため、これは望ましくない実装です。このクラスのクライアントは、特定のスクリプト フラグメントに名前でアクセスし、それらを数えたり列挙したりする理由はありません。

これを次のように実装できました。

public string ScriptGet(string name)
public void ScriptSet(string name, string value)

これはおそらく持つべきものですが、これは、欠落している機能の代わりにインデックス付きクラスを使用することが合理的な代用ではないことが多い理由を示す便利な例です。

同様の機能をインデックス付きプロパティとして実装するために、以下のコードを作成する必要がありました。これはかなり長く、より複雑で、読み取り、理解、保守が難しいことに気付くでしょう。

public interface IConfig
{
    // Other configuration properties removed for examp[le

    /// <summary>
    /// Script fragments
    /// </summary>
    ScriptsCollection Scripts { get; }
}

/// <summary>
/// Class to handle loading and saving the application's configuration.
/// </summary>
internal class Config : IConfig, IXmlConfig
{
    public Config()
    {
        _scripts = new ScriptsCollection();
        _scripts.ScriptChanged += ScriptChanged;
    }

  #region Application Configuraiton Settings

    // Other configuration properties removed for examp[le

    /// <summary>
    /// Script fragments
    /// </summary>
    public ScriptsCollection Scripts
    { get { return _scripts; } }
    private readonly ScriptsCollection _scripts;

    private void ScriptChanged(object sender, ScriptChangedEventArgs e)
    {
        OnAppConfigChanged();
    }

  #endregion

    /// <summary>
    /// Clears configuration settings, but does not clear internal configuration meta-data.
    /// </summary>
    private void ClearConfig()
    {
        // Other properties removed for example
        _scripts.Clear();
    }

  #region IXmlConfig

    void IXmlConfig.XmlSaveTo(int configVersion, XElement appElement)
    {
        Debug.Assert(configVersion == 2);
        Debug.Assert(appElement != null);

        // Saving of other properties removed for example

        if (_scripts.Count > 0)
        {
            var scripts = new XElement("Scripts");
            foreach (var kvp in _scripts)
            {
                var scriptElement = new XElement(kvp.Key, kvp.Value);
                scripts.Add(scriptElement);
            }
            appElement.Add(scripts);
        }
    }

    void IXmlConfig.XmlLoadFrom(int configVersion, XElement appElement)
    {
        // Implementation simplified for example

        Debug.Assert(appElement != null);
        ClearConfig();
        if (configVersion == 2)
        {
            // Loading of other configuration properites removed for example

            var scripts = appElement.Element("Scripts");
            if (scripts != null)
                foreach (var script in scripts.Elements())
                    _scripts[script.Name.ToString()] = script.Value;
        }
        else
            throw new ApplicaitonException("Unknown configuration file version " + configVersion);
    }

  #endregion
}

public class ScriptsCollection : IEnumerable<KeyValuePair<string, string>>
{
    private readonly Dictionary<string, string> Scripts = new Dictionary<string, string>();

    public string this[string name]
    {
        get
        {
            if (!string.IsNullOrWhiteSpace(name))
            {
                string script;
                if (Scripts.TryGetValue(name.Trim().ToLower(), out script))
                    return script;
            }
            return string.Empty;
        }
        set
        {
            if (!string.IsNullOrWhiteSpace(name))
                Scripts[name.Trim().ToLower()] = value;
        }
    }

    public void Clear()
    {
        Scripts.Clear();
    }

    public int Count
    {
        get { return Scripts.Count; }
    }

    public event EventHandler<ScriptChangedEventArgs> ScriptChanged;

    protected void OnScriptChanged(string name)
    {
        if (ScriptChanged != null)
        {
            var script = this[name];
            ScriptChanged.Invoke(this, new ScriptChangedEventArgs(name, script));
        }
    }

  #region IEnumerable

    public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
    {
        return Scripts.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

  #endregion
}

public class ScriptChangedEventArgs : EventArgs
{
    public string Name { get; set; }
    public string Script { get; set; }

    public ScriptChangedEventArgs(string name, string script)
    {
        Name = name;
        Script = script;
    }
}
于 2014-04-05T18:45:05.180 に答える
2

別の回避策は、 C# でのインデックス作成をサポートするプロパティの簡単な作成 にリストされており、作業が少なくて済みます。

編集: また、元の質問に答えて、ライブラリのサポートを使用して目的の構文を実現できる場合は、それを言語に直接追加する非常に強力なケースが必要だと思うことも追加する必要があります。言語の肥大化を最小限に抑えます。

于 2010-07-27T14:45:14.863 に答える
1

設計、実装、テスト、文書化のコストに見合わないため、追加しなかったと言えます。

冗談はさておき、これはおそらく、回避策が単純であり、この機能によって時間対利益が削減されることがないためです。ただし、これが将来の変更として表示されても驚かないでしょう。

また、より簡単な回避策は通常のメソッドを作成することであることを忘れていました。

public void SetFoo(int index, Foo toSet) {...}
public Foo GetFoo(int index) {...}
于 2010-05-10T22:38:25.047 に答える
1

ラムダを使用してインデックス作成機能をプロキシする簡単な一般的なソリューションがあります

読み取り専用のインデックス作成の場合

public class RoIndexer<TIndex, TValue>
{
    private readonly Func<TIndex, TValue> _Fn;

    public RoIndexer(Func<TIndex, TValue> fn)
    {
        _Fn = fn;
    }

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

可変インデックスの場合

public class RwIndexer<TIndex, TValue>
{
    private readonly Func<TIndex, TValue> _Getter;
    private readonly Action<TIndex, TValue> _Setter;

    public RwIndexer(Func<TIndex, TValue> getter, Action<TIndex, TValue> setter)
    {
        _Getter = getter;
        _Setter = setter;
    }

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

そして工場

public static class Indexer
{
    public static RwIndexer<TIndex, TValue> Create<TIndex, TValue>(Func<TIndex, TValue> getter, Action<TIndex, TValue> setter)
    {
        return new RwIndexer<TIndex, TValue>(getter, setter);
    } 
    public static RoIndexer<TIndex, TValue> Create<TIndex, TValue>(Func<TIndex, TValue> getter)
    {
        return new RoIndexer<TIndex, TValue>(getter);
    } 
}

自分のコードでは、次のように使用します

public class MoineauFlankContours
{

    public MoineauFlankContour Rotor { get; private set; }

    public MoineauFlankContour Stator { get; private set; }

     public MoineauFlankContours()
    {
        _RoIndexer = Indexer.Create(( MoineauPartEnum p ) => 
            p == MoineauPartEnum.Rotor ? Rotor : Stator);
    }
    private RoIndexer<MoineauPartEnum, MoineauFlankContour> _RoIndexer;

    public RoIndexer<MoineauPartEnum, MoineauFlankContour> FlankFor
    {
        get
        {
            return _RoIndexer;
        }
    }

}

MoineauFlankContours のインスタンスを使用して実行できます

MoineauFlankContour rotor = contours.FlankFor[MoineauPartEnum.Rotor];
MoineauFlankContour stator = contours.FlankFor[MoineauPartEnum.Stator];
于 2014-05-28T07:51:29.343 に答える
0

ここに示すように、明示的に実装されたインターフェイスを使用してこれを実現できることもわかりました: C# の名前付きインデックス付きプロパティ? (その返信に示されている2番目の方法を参照してください)

于 2016-05-30T03:13:27.550 に答える