Indexer named “Item” required by interface but not possible to implement?

心已入冬 提交于 2021-02-19 02:37:20

问题


I'm trying to implement an interface of a class in the ESPRIT api that requires an indexer named "Item." (I'm guessing the interface came from VB.NET but I don't have the source.) Obviously the "Item[index]" indexer is auto-generated by the compiler by default but I'm getting the following errors:

I realize [System.Runtime.CompilerServices.IndexerName("Item")] is redundant; it's simply there to demonstrate explicitly that Item is generated and the error remains.

Attempting to implement public ToolBarControl Item[int index] 'splodes everything in the rest of the class.

Edit1: The third screenshot shows IToolBar.this but I did try IToolBar.Item with the result stated above.

Looks like I'm not the first to request named iterators but I think the generated named indexer should satisfy the requirement. https://github.com/dotnet/csharplang/issues/471

How do I implement this indexer to satisfy the interface?


Note: I'm making the following edit for completeness, future troubleshooting and to answer questions posed in comments but I already know the solution for this particular instance is implementing get_Item(int index) as stated in the accepted answer.

Edit2: To answer "what does visual studio suggest?" As you can see, it knows before it makes the replacement that Index will be in error because there is no Index parameter defined on Item (I've tested this and it indeed fails.) Neither of the other two options for automatic implementation work.

Edit3: From Object Browser, inside IToolBar, Item[int] is defined as:


回答1:


C# can satisfy that .Item 'indexer' from the Interface with get_Item. This is because of how Property/Index getters and setters are generated during IL compilation.

Here is how it is described in the CLI Specification:

I.10.4 Naming patterns

For Properties:

An individual property is created by deciding on the type returned by its getter method and the types of the getter’s parameters (if any). Then, two methods are created with names based on the name of the property and these types. For the examples below we define two properties: Name takes no parameters and returns a System.String, while Item takes a System.Object parameter and returns a System.Object. Item is referred to as an indexed property, meaning that it takes parameters and thus can appear to the user as through it were an array with indices.

PropertyGet, used to read the value of the property
   Pattern: <PropType> get_<PropName> (<Indices>)
   Example: System.String get_Name ();
   Example: System.Object get_Item (System.Object key);
PropertySet, used to modify the value of the property
   Pattern: void set_<PropName> (<Indices>, <PropType>)
   Example: void set_Name (System.String name);
   Example: void set_Item (System.Object key, System.Object value); 

Therefore you should be able to meet the conditions of the indexer implementing it with something like this:

public class ManagedEspritToolbar : Esprit.Toolbar
{
    public ToolbarControl get_Item(int index) => Toolbar[index];
}

For testing this you can create a simple interface in VB.NET:

Public Interface IVBNetInterface
    Property Item(index As Integer) As String
End Interface

Then implement the interface on a new class in C#. Note how it defaults to get_Item/set_Item accessors when allowing the IDE to auto-implement the interface:

public class CSharpClass : IVBNetInterface
{
    public string get_Item(int index)
    {
        throw new NotImplementedException();
    }

    public void set_Item(int index, string Value)
    {
        throw new NotImplementedException();
    }
}

Reading the generated IL of the Interface confirms this behavior:


What about VB.NET's Default Property?

In VB.NET, there is a Default property decorator which is essentially the mechanism for declaring an indexer on a class:

Public Interface IVBNetInterface
    Default Property Item(index As Integer) As String
End Interface

When this is implemented correctly on the VB.NET class/interface, the standard C# this[int] indexing implementation will work. Therefore the get_Item workaround should only really be necessary when the Default attribute has not been properly applied to the target index property. Note the addition of the System.Reflection.DefaultMemberAttribute attribute when investigating the IL code once this has been applied:


Improving Usage:

To get around underlying classes/interfaces not being written with the Default modifier, you can implement the interface indexers explicitely, which allows exposing a traditional C# styled indexer on the class:

public class CSharpClass : IVBNetInterface
{
    public string this[int index]
    {
        get => throw new NotImplementedException();
        set => throw new NotImplementedException();
    }

    #region IVBNetInterface

    string IVBNetInterface.get_Item(int index) => this[index];

    void IVBNetInterface.set_Item(int index, string value) => this[index] = value;

    #endregion
}

This may be the preferred approach if you want the usage of the class to be inferred through the typical indexer while still satisfying the underlying Interface.Item requirement.




回答2:


The problem is that there is no valid C# syntax for declaring a named indexer so you cannot satisfy that interface using C#. There have been several requests to implement named indexers in C# all of which have been ignored or outright refused by the C# language team. They appear to not want to implement this feature in C#. Maybe if they change their minds later you might get a shot.

The only way I can think to approach this currently is to create a VB.NET library with a class that implements the interface and acts as either a wrapper or a base class for your own class.



来源:https://stackoverflow.com/questions/51975202/indexer-named-item-required-by-interface-but-not-possible-to-implement

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!