C#抽象结构

Han*_*esh 11 c# oop inheritance struct

如何在C#中使用结构实现继承(或类似)?我知道抽象结构是不可能的,但我需要实现类似的东西.

我需要它作为结构,因为它必须是一个值类型.我需要继承,因为我需要一个通用数组和方法,我可以保证在那里.

我认为一个例子会有很长的路要走,所以有些代码不能编译,但会显示我想要实现的内容;

    abstract struct Vertex
    {
       abstract int SizeInBytes;
       abstract void SetPointers();
    }
    struct ColorVertex : Vertex
    {
       Vector3 Position;
       Vector4 Color;

       override int SizeInBytes //static
       {
          get { return (3 + 4) * 4; }
       }
       override void SetVertexPointers() //static
       {
           ...
       }
    }

class main
{
   static void main()
   {
      Vertex[] verts = new Vertex[3];
      for(int i = 0; i < 3; i++)
          verts[i] = new ColorVertex();

      verts[0].SetVertexPointers(); //should call ColorVertex.SetVertexPointers

      externalAPIcall(verts);
   }
}
Run Code Online (Sandbox Code Playgroud)

编辑:

我需要值类型的原因是我可以创建它们的数组,并将其作为顶点缓冲区传递给OpenGL.为此,数据需要直接包含在此数组中.

如果这根本无法实现,我会感到惊讶.

Jef*_*tes 7

在C#中,您可以使用接口来实现类似于值类型(结构)的多态,因为您无法直接从a派生,struct但您可以使用多种struct类型实现特定的接口.

因此,而不是你的抽象struct,Vertex你可以有一个接口,IVertex.

interface IVertex
{
    int SizeInBytes { get; }
    void SetPointers();
}
Run Code Online (Sandbox Code Playgroud)

但是,您需要实现自己的值类型是非常罕见的,因此在继续之前确保确实需要值类型语义.如果你确实实现了自己的值类型,请确保它们是不可变的,因为可变值类型是解决各种可怕问题的途径.

您应该知道从值类型转换为接口时会发生装箱.如果您的值类型是可变的(不要创建可变值类型),这不仅会产生影响,但这会减少,或者很可能取消使用值类型可能获得的任何内存优势,具体取决于您何时或如何使用这样做以及你是否为每一个值做这件事 - 如果你不确定的话,可以使用一个分析器.

  • 重要!如果从接口派生结构然后使用该接口,结构将被装箱! (9认同)
  • 值得注意的是,装箱将消除使用值类型提供的任何内存优势. (2认同)

Jon*_*eet 6

基本上你不能.您无法从结构派生.为什么你认为你想要一个结构而不是一个类?你说"它必须是一种价值类型" - 为什么?你是否同样​​认为继承是唯一的选择而不是(比如)组合?例如,您可以使用:

public struct Vertex
{
    // ...
}

public struct Color
{
    // ...
}

public struct ColorVertex
{
    private readonly Color color;
    private readonly Vertex vertex;

    // ...
}
Run Code Online (Sandbox Code Playgroud)

你根本无法得到一个"抽象结构"或任何类似的工作,所以我建议你解释你不满意的要求背后的原因,而不是仅仅说明它们是无法避免的要求.

  • @ justin.m.chase:a)那不是一个值类型,据说是必需的; b)当他创建一个类型为"Vertex []"的数组时,它将最终装入任何添加到它的值,这可能会删除使用结构*的一半*. (3认同)

Ada*_*son 6

根据您最近的编辑:

我需要值类型的原因是我可以创建它们的数组,并将其作为顶点缓冲区传递给OpenGL.为此,数据需要直接包含在此数组中.

对你来说,真正的解决方案似乎是封装.如果您的布局struct由第三方API决定(以便您可以与非托管代码进行交互),那么您应该考虑将API类型包装在适当的类中,而不是尝试直接在代码中与它们进行交互.

首先,你说:

我需要它作为结构,因为它必须是一个值类型.我需要继承,因为我需要一个通用数组和方法,我可以保证在那里.

这不会像你期望的那样.正如其他人所指出的,定义一组可应用于结构的常用功能的唯一方法是通过接口(例如,.NET工具中的基本类型IComparable).不幸的是,如果你要声明一个类型的数组IYourInterface,所有的值都将被装箱(接口引用是引用类型,即使它们指向的基础值是值类型).

例如,假设您声明了一个IVertex接口:

public interface IVertex
{
    int SizeInBytes { get; }
    void SetPointers();
}
Run Code Online (Sandbox Code Playgroud)

并且您有一个或多个实现它的值类型:

struct ColorVertex : IVertex
{
   Vector3 Position;
   Vector4 Color;

   override int SizeInBytes //static
   {
      get { return (3 + 4) * 4; }
   }
   override void SetVertexPointers() //static
   {
       ...
   }
}
Run Code Online (Sandbox Code Playgroud)

每当你这样做:

ColorVertex myVertex = new ColorVertex();

IVertex foo = myVertex;
Run Code Online (Sandbox Code Playgroud)

第二行将设置值的值myVertex并存储对该盒装值的引用foo.由于数组只是一系列变量,因此适用相同的规则:

IVertex[] foos = { myVertex };
Run Code Online (Sandbox Code Playgroud)

foos将装箱中的所有值,并存储它们的引用.这与你做的不同:

ColorVertex[] colors = { myVertex };
Run Code Online (Sandbox Code Playgroud)

没有拳击的地方.

这与你正在寻找的东西直接相关,因为现在装箱值意味着你不再拥有一个连续的价值块(嗯,你这样做,但是连续的块只是引用;值本身就在其他地方).

封装

鉴于你的事实

  1. 拥有您需要与之交互的已定义类型的第三方API
  2. 要求在代码中支持不同的用例,并希望使用面向对象的设计模式

您应该考虑包装OpenGL API.例如,假设您有以下内容:

// OpenGL type
struct Vertex
{
    int SizeInBytes;
}

public static extern void OpenGLFunction(Vertex[] vertices);
Run Code Online (Sandbox Code Playgroud)

可能更好的选择是定义自己的界面,然后隐藏OpenGL API:

public abstract class VertexBase
{
    internal Vertex ToVertex()
    {
        // your logic here
    }
}

public static class OpenGL
{
    public static void WrappedFunction(VertexBase[] vertices)
    {
        Vertex[] outVertices = new Vertex[vertices.Length];

        for(int i = 0; i < vertices.Length; i++)
        {
            outVertices[i] = vertices[i].ToVertex();
        }

        OpenGLFunction(outVertices);
    }
}
Run Code Online (Sandbox Code Playgroud)

(这显然是一个人为的例子,但是它应该展示我在你的代码和其他API之间引入一层抽象的方法.