如何在 c++/cli 中调用 base class indexer 属性?

How to call base class indexer property in c++/cli?

我正在尝试实现一个 class 继承自泛型 List<T> class:

Class MyList::List<MyClass>

我想在我的一种方法中调用 List<T>::Item[Int32] 索引器 属性,但我不知道该怎么做。

我尝试了 List<MyClass>::[i]List<MyClass>::Item[i],但都不起作用。

我知道,要从 List<MyClass> 调用其他方法,例如 Add,我可以只做 List<MyClass>::Add(myInstance)

(在我的实际代码中,我打算实现一个存储图像列表的 class。根据某些情况,我的索引器将 return 原始图像或处理过的图像。因此,我需要在索引器的 getter 中包含一些逻辑。)

由于 List<T>.Item[Int32] 是默认索引器,您可以只执行 this[i]。例如:

public ref class MyList : System::Collections::Generic::List<MyClass ^>
{
public:
    int CountNonNull();
};

int MyList::CountNonNull()
{
    int nonNull = 0;
    for (int i = 0; i < Count; i++)
    {
        if (this[i] != nullptr)
            nonNull++;
    }
    return nonNull;
}

或者,如果您愿意,可以明确使用 "default indexer" 属性 名称 default[i]

if (this->default[i] != nullptr)
    nonNull++;

List<MyClass ^>::default[i] 也是有效的,可用于在覆盖索引器本身时调用基本索引器。

参见:How to: Use Properties in C++/CLI: Indexed properties


在您写的评论中,您确实打算覆盖 List<T>.Item[Int32] 默认索引器:

I intend to implement a class that stores a list of images. Depending on some conditions, my indexer would either return the raw image or a processed image.

如果是这样,我不建议从 List<T> 继承,因为 List<T>.Item[Int32] 不是虚拟的 并且不打算被覆盖。因此,您只能通过 接口重新实现 来隐藏它。但这会使您的应用程序面临一个可能的错误:如果您的 MyList 被向上转换为基础 class List<MyClass ^>,则不会调用替换索引器。更糟糕的是,List<T>.GetEnumerator() 返回的枚举器似乎不会调用您的替换索引器,因此简单地遍历 MyList 将导致返回未处理的项目。

要明白我的意思,请考虑以下合成示例:

ref class MyList;

public ref class MyClass
{
public:
    property bool Processed;
};

public ref class MyList : List<MyClass ^>, IList<MyClass ^>
{
public:
   // default indexer
    virtual property MyClass^ default[int] 
    {
        MyClass^ get(int index) new = IList<MyClass ^>::default::get  // hiding + interface reimplementation of default::get method
        { 
            MyClass^ item = List<MyClass ^>::default[index]; // Call base default indexer
            item = ProcessItem(item);                        // Add your custom logic here
            return item;                                     // return the item
        }
        void set(int index, MyClass^ value) new = IList<MyClass ^>::default::set // hiding + interface reimplementation of default::set method
        {
            List<MyClass ^>::default[index] = value;         // Call base default indexer
        }
    }

private:
    MyClass^ ProcessItem(MyClass ^item)
    {
        // Add your custom logic here
        if (item != nullptr)
            item->Processed = true;
        return item;
    }
};

此处get方法在返回前调用ProcessItem()进行处理。 (您的图像处理将在此处进行;在示例中,bool 设置为 true。)

现在考虑以下单元测试:

MyList ^list = gcnew MyList();

list->Add(gcnew MyClass());
for each (MyClass^ item in list)
    Debug::Assert(item->Processed); // FAILS because enumerator doesn't call indexer!

list->Add(gcnew MyClass());
Debug::Assert(list[list->Count-1]->Processed); // Passes because re-implemented Add() was called.

// Upcast to base class
List<MyClass ^>^ baseList = list;
baseList->Add(gcnew MyClass());
Debug::Assert(baseList[list->Count-1]->Processed); // FAILS because re-implemented Add() was NOT called!

// Upcast to interface
IList<MyClass ^>^ iList = list;
iList->Add(gcnew MyClass());
Debug::Assert(iList[iList->Count-1]->Processed); // Passes because re-implemented Add() was called.

虽然第二个和第四个断言通过,但第一个和第三个失败,因为替换 get() 方法没有被调用。这确实无法避免;通过 new 关键字的接口重新实现实际上并没有覆盖基本实现,它在 vtable 中创建了一个带有新插槽的新实现。参见:new (new slot in vtable) (C++/CLI and C++/CX).

那么,你有什么选择?

首先,如果在添加图像到集合时可以完成必要的图像处理,则可以继承System.Collections.ObjectModel.Collection<T>,它提供了被调用的受保护的虚拟方法每当从集合中添加或删除项目时。 (这是 ObservableCollection<T> 的基础 class。)

因此如果我们定义MyList如下:

public ref class MyList : Collection<MyClass ^>
{
protected:
    virtual void InsertItem(int index, MyClass ^ item) override
    {
        Collection<MyClass ^>::InsertItem(index, ProcessItem(item));
    }

    virtual void SetItem(int index, MyClass ^ item) override
    {
        Collection<MyClass ^>::InsertItem(index, ProcessItem(item));
    }

private:
    MyClass^ ProcessItem(MyClass ^item)
    {
        // Add your custom logic here
        if (item != nullptr)
            item->Processed = true;
        return item;
    }
};

然后所有四个断言现在都通过了:

MyList ^list = gcnew MyList();

list->Add(gcnew MyClass());
for each (MyClass^ item in list)
    Debug::Assert(item->Processed); // Passes

list->Add(gcnew MyClass());
Debug::Assert(list[list->Count-1]->Processed); // Passes

// Upcast to base class
Collection<MyClass ^>^ baseList = list;
baseList->Add(gcnew MyClass());
Debug::Assert(baseList[list->Count-1]->Processed); // Passes

// Upcast to interface
IList<MyClass ^>^ iList = list;
iList->Add(gcnew MyClass());
Debug::Assert(iList[iList->Count-1]->Processed); // Passes      

其次,如果图像处理必须get()方法中完成,您可以使用decorator pattern并创建自己的自定义集合实现List<MyClass ^> 包装底层 List<MyClass ^> 并在其自己的默认索引器、枚举器、CopyTo() 和其他根据需要访问项目的方法中进行必要的处理:

public ref class MyList : IList<MyClass ^>
{
private:
    List<MyClass ^> list;

public:
    MyList()
    {
        list = gcnew List<MyClass ^>();
    }

    virtual property MyClass^ default[int] 
    {
        MyClass^ get(int index)
        {
            MyClass^ item = list[int];
            item = ProcessItem(item); // Add your custom logic here                
            return item;                                     
        }
        void set(int index, MyClass^ value)
        {
            list[index] = value;
        }
    }

    // Implement all other methods as required.
    virtual property int Count { int get() { return list->Count; } }

    // Etc
};

现在基础列表包含在您的翻译列表中,"raw" 图像不可能被访问。