简体   繁体   English

如何使用动态从非泛型类调用泛型类的方法?

[英]How to call method of generic class from non generic class using dynamic?

I want to call a method of generic class from non generic class which doesn't know the type. 我想从不知道类型的非泛型类中调用泛型类的方法。

interface IShape
{
    SomeType AProperty;     // Edit 2, sorry this should be a property.
}

interface IShapeEditor
{
}

class ShapeEditorBase : IShapeEditor
{
}

class RectangleEditor<T> : ShapeEditorBase where T : IShape
{
    void Offset(T shape, int x, int y)
    {
    }
}

// This class must not be a generic class.
class OffsetRectangleButton
{
    void ButtonPressed(IShapeEditor editor)
    {
        IShape shape = ashape; // from somewhere
        dynamic editorInstance = editor;
        editorInstance.Offset(shape, 1, 1); // Will trigger an exception here!
    }
}

But it triggered an exception, it said C# cannot find Offset method in ShapeEditorBase. 但是它触发了一个异常,它说C#在ShapeEditorBase中找不到Offset方法。
Why is it not the RectangleEditor, but ShapeEditorBase? 为什么不是RectangleEditor,而是ShapeEditorBase?
How can I solve that problem? 我该如何解决这个问题? Or maybe a refactoring suggestion? 还是重构建议?

If possible without reflection (I heard it is not good). 如果可能没有反射(我听说这不好)。

=================== ===================

Edit for additional information: 编辑以获取其他信息:
- All code snippets are in a framework which is a Control. -所有代码段都在一个控件框架中。
- Application gives type via generic -应用程序通过通用类型
- OffsetRectangleButton or other buttons for rectangle know everything about the methods because they are in a component/dll. -OffsetRectangleButton或其他矩形按钮知道方法的所有知识,因为它们位于组件/ dll中。

==================== ====================

Edit 3 编辑3
- Make the code as similar as possible to the problem -使代码与问题尽可能相似
- I rewrite the code to make the previous answers understandable instead of editing the previous code -我重写代码以使先前的答案易于理解,而不是编辑先前的代码
- Please see OffsetRectangleAction -请参阅OffsetRectangleAction
- Question: Is the interface IRectangleEditor is correct? -问题:接口IRectangleEditor是否正确? (And I can continue to solve OffsetRectangleAction) (而且我可以继续解决OffsetRectangleAction)
- Question: Or there are better solution? -问题:还是有更好的解决方案?

// =========================================
// Shape Display Component (a Winform Panel)
// =========================================

// In reality, rectangle is very complex implementation.
interface IRectangle // For rectangle display
{
    Rectangle Rectangle { get; set; }
}

interface ILine // ICircle etc for other displays
{
    Line Line { get; set; }
}

// Edit/Offset actions interface. It is represented by a button.
interface IAction
{
    void Run(IShapeDisplay display);
}

// Container of all display's actions.
interface IActionContainer
{
    IList<IAction> Actions; // Every action is related to a control (button, edit box, etc).
}   

// Please see "OffsetRectangleAction".
// This class is container of all RectangleDisplays actions (as buttons in UI).
// This is also important, because this class shows why OffsetRectangleAction must not be generic.
// If OffsetRectangleAction is generic, then this class must be generic because it has OffsetRectangleAction.
// If this class generic, then in user interface has more than one OffsetRectangleAction buttons are created.
// And each OffsetRectangleAction edit certain type of Rectangle generic.
class RectangleActionContainer : IActionContainer
{
    // List of all actions/buttons
    IList<IAction> Actions;    

    RectangleActionContainer()
    {
        // It must be able to edit various types of rectangle.
        this.Actions.Add(new EditAction());
        this.Actions.Add(new OffsetRectangleAction());
    }
}

// This class must not be a generic class. Please see RectangleActionContainer class.
// This is the most important problem!!!!!!!!!!!!!!!!!!!!!!!!!
// How can I access RectangleDisplay.RetctangleEditor.Offset or Edit
class OffsetRectangleAction: IAction // Similar also EditRectangleAction, InflateRectangleAction
{
    void Run(IShapeDisplay display)
    {
        IRectangle rectangle = this.GelectedRectangle(); // from somewhere

        // How to make it runs properly without exception.
        dynamic rectangleDisplayInstance = display;
        dynamic editHandler = rectangleDisplayInstance.EditHandler;
        editHandler.Offset(rectangle, 1, 1); // Will trigger an exception here!
    }
}

// RectangleEditor interface to edit rectangles and as rectangles container.
interface IRectangleEditor<T> where T : IRectangle // Generic because there are many type rectangle for various purposes. 
{
    IList<T> Rectangles { get; } // List of rectangle to be edited/visualized.

    Edit(T rect, Rectangle dimension);  

    Offset(T rect, double x, double y);
}

// Shape display interface
interface IShapeDisplay
{
    IActionContainer ActionContainer { get; }

    void Draw(Graphics canvas);
}

// Base of all displays (rectangle, line, circle)
class ShapeDisplayBase : IShapeDisplay
{
    IActionContainer ActionContainer { get; private set; }

    virtual void Draw(Graphics canvas)
    {
    }
}

// Rectangle Display.
// Controller for displaying and bussines logic of rectangle shapes.
// ShapeDisplayBase inherited also for Line, Circular etc displays.
class RectangleDisplay<T> : ShapeDisplayBase where T : IShape
{
    IRectangleEditor<T> RectangleEditor { get; private set; }

    RectangleDisplay<T>(IRectangleEditor<T> editor)
    {
        this.RectangleEditor = editor;
    }

    override void Draw(Graphics canvas)
    {
    }
}

// Controller for the whole shape displays (Rectangle, Line, Circle etc.)
class ShapeDisplaysController
{
    IList<IActionContainer> ActionContainers { get; private set; } // Action container for all displays (rectangle, line, etc)

    IList<IShapeDisplay> ShapeDisplays { get; private set; } // Can be RectangleDisplay, LineDisplay, CircularDisplay

    void RegisterDisplay(IShapeDisplay display)
    {
       this.ShapeDisplays = display;

       // A type of displays can only one action container (eg: many rectangle types can be edited only with one edit button)
       if (!ActionContainer.Contains(display))
       {
           ActionContainers.Add(display.ActionContainer);
       }
    }

    // Show all shape display objects.
    void Draw(Graphics canvas)
    {
        foreach(var display in this.ShapeDisplays)
        {
            display.Draw(canvas);
        }
    }
}

// ==========================
// Application 
// ==========================

class RoundedRectangleEditHandler<T> : IRectangleEditor<T> where T : IRectangle
{
     IList<T> Rectangles { get; private set;} // List of rectangle to be edited/visualized.
     implementation...
}

class FillRectangleEditHandler<T> : IRectangleEditor<T> where T : IRectangle
{
     IList<T> Rectangles { get; private set;} // List of rectangle to be edited/visualized.
     implementation...
}

// There are many application that use ShapeViewer component.
// And every application has their own rectangle type, so we decided to use generic.
class ShapeViewerApp: Form
{
    // Visualization control for shape.
    private ShapeDisplaysController myShapeDisplaysController;

    // Show shape list in grid.
    private GridController myGridController;

    static void Main()
    {
        this.myShapeDisplaysController = new ShapeDisplaysController();

         var roundedRectangleEditHandler = new RoundedRectangleEditHandler<RoundedRect>();
         var roundedRectangleDisplay = new RoundedRectangleDisplay<RoundedRect>(roundedRectangleEditHandler);

         var fillRectangleEditHandler = new FillRectangleEditHandler<FillRectangle>();
         var fillRectangleDisplay = new FillRectangleDisplay<FillRectangle>(fillRectangleEditHandler);           

         this.myShapeDisplaysController.Register(fillRectangleDisplay);
         this.myShapeDisplaysController.Register(roundedRectangleDisplay);
         this.myShapeDisplaysController.Register(lineDisplay);
         this.myShapeDisplaysController.Register(circleDisplay);

         // ShapeDisplaysController is a Winform Panel UI.
         this.Controls.Add(this.myShapeDisplaysController.Control);
         this.ShowDialog();
    }
}

IShapeEditor doesn't contain your Offset method, and thus the program doesn't know that the method exists on that object. IShapeEditor不包含您的Offset方法,因此程序不知道该方法存在于该对象上。 You could cast it as that type, if you were certain that's what it is and that would enable you to avoid the dynamic keyword. 如果可以确定它是这种类型,并且可以避免使用dynamic关键字,则可以将其强制转换为该类型。

I suggest you add the method to IShapeEditor , or otherwise use the exact type you care about, since the class you're implementing refers to Rectangle s, it only makes sense that it would be concerned about only Rectangle s, and not any other IShape . 我建议您将方法添加到IShapeEditor ,或者以其他方式使用您关心的确切类型,因为您实现的类引用的是Rectangle ,因此仅考虑Rectangle而不关心任何其他IShape才有意义。

How about moving you Offset to IShapeEditor and implement it in ShapeEditorBase ? 如何将Offset移动到IShapeEditor并在ShapeEditorBase实现呢? Then you wouldn't need to use dynamic . 然后,您无需使用dynamic

class ShapeEditorBase : IShapeEditor
{
    public void Offset(IShape shape, int x, int y)
    {
    }
}

class RectangleEditor<T> : ShapeEditorBase where T : IShape
{
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM