Tại sao C # không triển khai các thuộc tính được lập chỉ mục?


83

Tôi biết, tôi biết ... Câu trả lời của Eric Lippert cho loại câu hỏi này thường là " bởi vì nó không đáng với chi phí thiết kế, thực hiện, thử nghiệm và ghi lại nó ".

Nhưng tôi vẫn muốn có một lời giải thích tốt hơn ... Tôi đang đọc bài đăng trên blog này về các tính năng mới của C # 4 và trong phần về COM Interop, phần sau thu hút sự chú ý của tôi:

Nhân tiện, đoạn mã này sử dụng một tính năng mới nữa: thuộc tính được lập chỉ mục (hãy xem kỹ các dấu ngoặc vuông sau Phạm vi.) Nhưng tính năng này chỉ khả dụng cho COM interop; bạn không thể tạo các thuộc tính được lập chỉ mục của riêng mình trong C # 4.0 .

Được rôi nhưng tại sao ? Tôi đã biết và rất tiếc vì không thể tạo các thuộc tính được lập chỉ mục trong C #, nhưng câu này khiến tôi nghĩ lại về nó. Tôi có thể thấy một số lý do chính đáng để triển khai nó:

  • CLR hỗ trợ nó (ví dụ: PropertyInfo.GetValuecó một indextham số), vì vậy thật đáng tiếc chúng ta không thể tận dụng nó trong C #
  • nó được hỗ trợ cho COM interop, như được hiển thị trong bài viết (sử dụng điều phối động)
  • nó được thực hiện trong VB.NET
  • đã có thể tạo chỉ mục, tức là áp dụng chỉ mục cho chính đối tượng, vì vậy có lẽ sẽ không có vấn đề gì lớn nếu mở rộng ý tưởng cho các thuộc tính, giữ nguyên cú pháp và chỉ thay thế thisbằng tên thuộc tính.

Nó sẽ cho phép viết những thứ như vậy:

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

Hiện tại, cách giải quyết duy nhất mà tôi biết là tạo một lớp bên trong ( ValuesCollectionví dụ) thực hiện một trình chỉ mục và thay đổi thuộc Valuestính để nó trả về một thể hiện của lớp bên trong đó.

Điều này rất dễ làm, nhưng khó chịu ... Vì vậy, có lẽ trình biên dịch có thể làm điều đó cho chúng ta! Một tùy chọn sẽ là tạo một lớp bên trong triển khai trình chỉ mục và hiển thị nó thông qua một giao diện chung chung:

// 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;
        }
    }
}

Nhưng tất nhiên, trong trường hợp đó thuộc tính thực sự sẽ trả về a IIndexer<int, string>, và sẽ không thực sự là thuộc tính được lập chỉ mục ... Sẽ tốt hơn nếu tạo thuộc tính được lập chỉ mục CLR thực.

Bạn nghĩ sao ? Bạn có muốn xem tính năng này trong C # không? Nếu không, tại sao?


1
Tôi có cảm giác đây là một trong những vấn đề "chúng tôi nhận được yêu cầu cho X nhưng không nhiều hơn cho Y" .
ChaosPandion

1
@ChaosPandion, vâng, bạn có thể đúng ... Nhưng tính năng này có thể sẽ khá dễ thực hiện và mặc dù nó chắc chắn không phải là "phải có", nhưng nó chắc chắn thuộc danh mục "tốt khi có"
Thomas Levesque

4
Các lập chỉ mục đã hơi khó chịu theo quan điểm CLR. Họ thêm một trường hợp ranh giới mới vào mã muốn làm việc với các thuộc tính, vì bây giờ bất kỳ thuộc tính nào cũng có thể có các tham số của trình lập chỉ mục. Tôi nghĩ việc triển khai C # có ý nghĩa, vì khái niệm mà một trình chỉ mục thường đại diện không phải là thuộc tính của một đối tượng, mà là 'nội dung' của nó. Nếu bạn cung cấp thuộc tính trình lập chỉ mục tùy ý, bạn ngụ ý rằng lớp có thể có các nhóm nội dung khác nhau, điều này tự nhiên dẫn đến việc đóng gói nội dung con phức tạp như một lớp mới. Câu hỏi của tôi là: tại sao CLR cung cấp các thuộc tính được lập chỉ mục?
Dan Bryant

1
@tk_ cảm ơn nhận xét mang tính xây dựng của bạn. Bạn có đăng nhận xét tương tự cho tất cả các bài viết về ngôn ngữ không phải là Free Pascal không? Vâng, tôi hy vọng nó làm cho bạn cảm thấy tốt về bản thân ...
Thomas Levesque

3
Đây là một trong số ít các tình huống mà C ++ / CLI và VB.net tốt hơn C #. Tôi đã triển khai rất nhiều thuộc tính được lập chỉ mục trong mã C ++ / CLI của mình và bây giờ khi chuyển đổi nó sang C #, tôi phải tìm giải pháp thay thế cho tất cả chúng. :-( SUCKS !!! // của bạn Nó sẽ cho phép để viết rằng loại điều là những gì tôi đã thực hiện trong những năm.
Tobias Knauss

Câu trả lời:


122

Đây là cách chúng tôi thiết kế C # 4.

Đầu tiên, chúng tôi lập danh sách mọi tính năng khả thi mà chúng tôi có thể nghĩ đến để thêm vào ngôn ngữ.

Sau đó, chúng tôi chèn các tính năng thành "điều này thật tệ, chúng tôi không bao giờ được làm điều đó", "điều này thật tuyệt vời, chúng tôi phải làm nó", và "điều này là tốt nhưng chúng ta đừng làm điều đó lần này".

Sau đó, chúng tôi xem xét số ngân sách chúng tôi có để thiết kế, thực hiện, thử nghiệm, lập tài liệu, vận chuyển và duy trì các tính năng "phải có" và phát hiện ra rằng chúng tôi đã vượt quá ngân sách 100%.

Vì vậy, chúng tôi đã chuyển một loạt nội dung từ nhóm "phải có" sang nhóm "tốt khi có".

Các thuộc tính được lập chỉ mục không bao giờ ở gần đầu danh sách "phải có". Họ rất thấp trong danh sách "tốt đẹp" và tán tỉnh trong danh sách "ý tưởng tồi".

Mỗi phút chúng ta dành để thiết kế, triển khai, thử nghiệm, ghi lại hoặc duy trì tính năng tốt đẹp X là một phút chúng ta không thể dành cho các tính năng tuyệt vời A, B, C, D, E, F và G. Chúng ta phải ưu tiên một cách tàn nhẫn để chúng ta chỉ làm các tính năng tốt nhất có thể. Các thuộc tính được lập chỉ mục sẽ rất tốt, nhưng không phải ở đâu cũng đủ tốt để thực sự được triển khai.


20
Tôi có thể thêm một phiếu bầu để đưa nó vào danh sách không tốt không? Tôi không thực sự thấy cách triển khai hiện tại có nhiều hạn chế như thế nào khi bạn chỉ có thể hiển thị một kiểu lồng nhau triển khai một trình chỉ mục. Tôi sẽ tưởng tượng rằng bạn sẽ bắt đầu thấy rất nhiều hackery để cố gắng đánh bóng một cái gì đó vào databinding và các thuộc tính nên là phương thức.
Josh

11
Và hy vọng rằng INotifyPropertyChanged được triển khai tự động sẽ cao hơn trong danh sách so với các thuộc tính được lập chỉ mục. :)
Josh

3
@Eric, OK, đó là điều tôi nghi ngờ ... dù sao cũng cảm ơn vì đã trả lời! Tôi đoán tôi có thể sống mà không có tài sản được lập chỉ mục, như tôi đã làm trong nhiều năm;)
Thomas Levesque

5
@Martin: Tôi không phải là chuyên gia về cách xác định ngân sách của các nhóm phần mềm lớn. Câu hỏi của bạn nên được gửi đến Soma, Jason Zander hoặc Scott Wiltamuth, tôi tin rằng tất cả những người này thỉnh thoảng viết blog. So sánh của bạn với Scala là so sánh giữa táo với cam; Scala không có hầu hết các chi phí như C #; chỉ để nêu tên một, nó không có hàng triệu người dùng với các yêu cầu tương thích ngược cực kỳ quan trọng. Tôi có thể kể cho bạn nhiều yếu tố khác có thể gây ra sự khác biệt lớn về chi phí giữa C # và Scala.
Eric Lippert

12
+1: Mọi người có thể học được nhiều điều về quản lý các dự án phần mềm bằng cách đọc phần này. Và nó chỉ dài vài dòng.
Brian MacKay

22

AC # indexer một thuộc tính được lập chỉ mục. Nó được đặt tên Itemtheo mặc định (và bạn có thể tham khảo nó như vậy từ ví dụ VB), và bạn có thể thay đổi nó bằng IndexerNameAttribute nếu bạn muốn.

Tôi không rõ tại sao, cụ thể là tại sao nó lại được thiết kế theo cách đó, nhưng nó dường như là một hạn chế có chủ ý. Tuy nhiên, nó phù hợp với Nguyên tắc thiết kế khung, khuyến nghị cách tiếp cận của thuộc tính không được lập chỉ mục trả về một đối tượng có thể lập chỉ mục cho các tập hợp thành viên. Tức là "có thể lập chỉ mục" là một đặc điểm của một loại; nếu nó có thể lập chỉ mục theo nhiều cách, thì nó thực sự nên được chia thành nhiều loại.


1
Cảm ơn bạn! Tôi đã nhiều lần gặp lỗi khi triển khai các giao diện tương tác COM có một trình indxer mặc định (DISPID 0) nhập dưới dạng [int] này nhưng tên ban đầu không phải là "Item" (đôi khi đó là "item" hoặc "value" hoặc tương tự ). Điều này vẫn biên dịch và chạy, nhưng dẫn đến cảnh báo FxCop CA1033 InterfaceMethodsShouldBeCallableByChildTypes, các vấn đề tuân thủ CLS (số nhận dạng chỉ khác nhau trong trường hợp), v.v. vì tên không hoàn toàn phù hợp. [IndexerName] là tất cả những gì cần thiết, nhưng tôi chưa bao giờ tìm được.
puetzk

Cảm ơn bạn!!! Thuộc tính IndexerName cho phép tôi hoàn tất việc chuyển đổi một hợp ngữ VB sang C # mà không phá vỡ các chữ ký MSIL.
Jon Tirjan

15

Bởi vì bạn đã có thể làm điều đó, và nó buộc bạn phải suy nghĩ theo khía cạnh OO, việc thêm các thuộc tính được lập chỉ mục sẽ chỉ làm tăng thêm tiếng ồn cho ngôn ngữ. Và chỉ là một cách khác để làm một việc khác.

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

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

foo.Values[0]

Cá nhân tôi chỉ muốn xem một cách duy nhất để làm điều gì đó hơn là 10 cách. Nhưng tất nhiên đây là ý kiến ​​chủ quan.


2
+1, đây là một cách triển khai tốt hơn nhiều so với việc kết hợp ngôn ngữ với các cấu trúc VB5.
Josh

1
+1 vì đây là cách tôi sẽ làm điều đó. Và nếu bạn giỏi, bạn có thể làm điều này chung chung.
Tony

10
Một vấn đề với cách tiếp cận đó là mã khác có thể tạo một bản sao của trình chỉ mục và không rõ ngữ nghĩa sẽ như thế nào nếu nó làm vậy. Nếu mã cho biết "var userList = Foo.Users; Foo.RemoveSomeUsers (); someUser = userList [5];" đó nên là phần tử từng là phần tử [5] của Foo (trước RemoveSomeUsers) hay sau này? Nếu một userList [] là một thuộc tính được lập chỉ mục, nó sẽ không phải được hiển thị trực tiếp.
supercat

Bạn có thích phân bổ quá độ?
Joshua

9

Tôi sử dụng để ủng hộ ý tưởng của các thuộc tính được lập chỉ mục nhưng sau đó nhận ra nó sẽ làm tăng thêm sự nhập nhằng kinh khủng và thực sự disincentivize chức năng. Các thuộc tính được lập chỉ mục có nghĩa là bạn không có phiên bản tập hợp con. Điều đó vừa tốt vừa xấu. Sẽ ít rắc rối hơn khi triển khai và bạn không cần tham chiếu trở lại lớp chủ sở hữu bao quanh. Nhưng nó cũng có nghĩa là bạn không thể chuyển tập hợp con đó cho bất cứ thứ gì; bạn có thể phải liệt kê mọi lúc. Bạn cũng không thể làm gì trước về nó. Tệ nhất là bạn không thể biết được khi nhìn vào một thuộc tính được lập chỉ mục là thuộc tính đó hay thuộc tính bộ sưu tập.

Ý tưởng là hợp lý nhưng nó chỉ dẫn đến sự thiếu linh hoạt và đột ngột khó xử.


Những suy nghĩ thú vị, ngay cả khi câu trả lời hơi muộn;). Bạn đang tạo ra những điểm rất tốt, +1.
Thomas Levesque

Trong vb.net, một lớp có thể có cả thuộc tính được lập chỉ mục và không được lập chỉ mục có cùng tên [ví dụ: Bar]. Biểu thức Thing.Bar(5)sẽ sử dụng thuộc tính được lập chỉ mục Bartrên Thing, trong khi (Thing.Bar)(5)sẽ sử dụng thuộc tính không được lập chỉ mục Barvà sau đó sử dụng trình lập chỉ mục mặc định của đối tượng kết quả. Đối với ràng buộc của tôi, việc cho phép Thing.Bar[5]trở thành một tài sản của Thing, thay vì một tài sản của Thing.Bar, là tốt vì, trong số những thứ khác, có thể tại một thời điểm nào đó, ý nghĩa của Thing.Bar[4]có thể rõ ràng, nhưng tác dụng thích hợp của ...
supercat

... một cái gì đó var temp=Thing.Bar; do_stuff_with_thing; var q=temp[4]có thể không rõ ràng. Cũng hãy xem xét khái niệm Thingcó thể giữ dữ liệu Bartrong một trường có thể là đối tượng bất biến được chia sẻ hoặc đối tượng có thể thay đổi không được chia sẻ; nỗ lực ghi vào Barkhi trường sao lưu là bất biến sẽ tạo ra một bản sao có thể thay đổi, nhưng cố gắng đọc từ đó thì không. Nếu Barlà một thuộc tính được lập chỉ mục có tên, bộ lấy chỉ mục có thể để nguyên bộ sưu tập sao lưu (cho dù có thể thay đổi hay không) trong khi bộ định có thể tạo một bản sao có thể thay đổi mới nếu cần.
supercat

người lập chỉ mục của một thuộc tính không phải là một điều tra viên - nó là một khóa
George Birbilis

6

Tôi thấy việc thiếu các thuộc tính được lập chỉ mục rất khó chịu khi cố gắng viết mã ngắn gọn, rõ ràng. Thuộc tính được lập chỉ mục có nội hàm rất khác với việc cung cấp một tham chiếu lớp được lập chỉ mục hoặc cung cấp các phương thức riêng lẻ. Tôi thấy hơi phiền khi cung cấp quyền truy cập vào một đối tượng bên trong triển khai một thuộc tính được lập chỉ mục thậm chí còn được coi là có thể chấp nhận được vì điều đó thường phá vỡ một trong những thành phần chính của hướng đối tượng: tính đóng gói.

Tôi gặp phải vấn đề này thường xuyên, nhưng tôi lại gặp phải nó hôm nay nên tôi sẽ cung cấp một ví dụ về mã trong thế giới thực. Giao diện và lớp được viết lưu trữ cấu hình ứng dụng là một tập hợp các thông tin liên quan lỏng lẻo. Tôi cần thêm các đoạn tập lệnh được đặt tên và việc sử dụng trình lập chỉ mục lớp không tên sẽ ngụ ý một ngữ cảnh rất sai vì các đoạn tập lệnh chỉ là một phần của cấu hình.

Nếu các thuộc tính được lập chỉ mục có sẵn trong C #, tôi có thể đã triển khai mã bên dưới (cú pháp là [key] này được thay đổi thành 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
}

Thật không may, các thuộc tính được lập chỉ mục không được triển khai vì vậy tôi đã triển khai một lớp để lưu trữ chúng và cung cấp quyền truy cập vào đó. Đây là một triển khai không mong muốn vì mục đích của lớp cấu hình trong mô hình miền này là đóng gói tất cả các chi tiết. Khách hàng của lớp này sẽ truy cập các đoạn script cụ thể theo tên và không có lý do gì để đếm hoặc liệt kê chúng.

Tôi có thể đã thực hiện điều này như:

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

Điều mà tôi có lẽ nên có, nhưng đây là một minh họa hữu ích về lý do tại sao việc sử dụng các lớp được lập chỉ mục để thay thế cho tính năng bị thiếu này thường không phải là một sự thay thế hợp lý.

Để triển khai khả năng tương tự như một thuộc tính được lập chỉ mục, tôi phải viết đoạn mã dưới đây mà bạn sẽ nhận thấy là dài hơn, phức tạp hơn đáng kể và do đó khó đọc, hiểu và duy trì hơn.

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;
    }
}

2

Một giải pháp khác được liệt kê tại Dễ dàng tạo các thuộc tính hỗ trợ lập chỉ mục trong C # , yêu cầu ít công việc hơn.

CHỈNH SỬA : Tôi cũng nên nói thêm rằng để trả lời cho câu hỏi ban đầu, rằng nếu chúng ta có thể hoàn thành cú pháp mong muốn, với sự hỗ trợ của thư viện, thì tôi nghĩ cần phải có một trường hợp rất mạnh để thêm nó trực tiếp vào ngôn ngữ, để giảm thiểu sự cồng kềnh ngôn ngữ.


2
Trả lời cho chỉnh sửa của bạn: Tôi không nghĩ nó sẽ gây ra sự cồng kềnh về ngôn ngữ; cú pháp đã có sẵn cho các trình chỉ mục lớp ( this[]), họ chỉ cần cho phép một mã định danh thay vì this. Nhưng tôi nghi ngờ nó bao giờ sẽ được đưa vào ngôn ngữ, vì những lý do giải thích bởi Eric trong câu trả lời của mình
Thomas Levesque

1

Tôi có thể nói rằng họ đã không thêm nó vào vì nó không đáng với chi phí thiết kế, thực hiện, thử nghiệm và ghi lại nó.

Nói đùa sang một bên, có lẽ vì cách giải quyết đơn giản và tính năng không bao giờ khiến thời gian so với lợi ích bị cắt giảm. Tuy nhiên, tôi sẽ không ngạc nhiên khi thấy điều này xuất hiện dưới dạng một sự thay đổi.

Bạn cũng quên đề cập rằng một cách giải quyết dễ dàng hơn chỉ là tạo một phương pháp thông thường:

public void SetFoo(int index, Foo toSet) {...}
public Foo GetFoo(int index) {...}

Rất đúng. Nếu cú ​​pháp thuộc tính là hoàn toàn quan trọng thì bạn có thể sử dụng giải pháp thay thế của Ion (có thể với một số generic để cho phép nhiều kiểu trả về). Bất kể, tôi nghĩ rằng điều này nói lên sự dễ dàng tương đối để hoàn thành công việc tương tự mà không cần các tính năng ngôn ngữ bổ sung.
Ron Warholic

1

Có một giải pháp chung đơn giản sử dụng lambdas để ủy quyền cho chức năng lập chỉ mục

Để lập chỉ mục chỉ đọc

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);
        }
    }
}

Để lập chỉ mục có thể thay đổ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);
        }
    }
}

và một nhà máy

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);
    } 
}

trong mã của riêng tôi, tôi sử dụng nó như

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;
        }
    }

}

và với một phiên bản của MoineauFlankContours tôi có thể làm

MoineauFlankContour rotor = contours.FlankFor[MoineauPartEnum.Rotor];
MoineauFlankContour stator = contours.FlankFor[MoineauPartEnum.Stator];

Thông minh, nhưng nó có lẽ sẽ tốt hơn để bộ nhớ cache indexer thay vì tạo ra nó mỗi lần;)
Thomas Levesque

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.