C#抽象结构

时间:2011-02-15 18:36:12

标签: 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);
   }
}

编辑:

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

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

9 个答案:

答案 0 :(得分:7)

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

因此,您可以拥有一个界面struct,而不是您的抽象VertexIVertex

interface IVertex
{
    int SizeInBytes { get; }
    void SetPointers();
}

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

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

答案 1 :(得分:6)

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

public struct Vertex
{
    // ...
}

public struct Color
{
    // ...
}

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

    // ...
}

你只是能够得到一个“抽象结构”或任何类似的工作,所以我建议你解释你不满意的要求背后的原因而不只是将它们列为无法避免的要求。

答案 2 :(得分:6)

根据您最近的编辑:

  

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

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

首先,你说明一下:

  

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

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

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

public interface IVertex
{
    int SizeInBytes { get; }
    void SetPointers();
}

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

struct ColorVertex : IVertex
{
   Vector3 Position;
   Vector4 Color;

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

每当你这样做时:

ColorVertex myVertex = new ColorVertex();

IVertex foo = myVertex;

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

IVertex[] foos = { myVertex };

foos中的所有值都将被加框,并存储其引用。这与你做的不同:

ColorVertex[] colors = { myVertex };

不需要拳击的地方。

这与您正在寻找的内容直接相关,因为现在装箱值意味着您不再拥有连续的值块(嗯,你这样做,但连续的块只是参考;价值本身就在别处)。

封装

鉴于您

  1. 拥有您需要与
  2. 交互的已定义类型的第三方API
  3. 支持代码中不同用例并希望使用面向对象设计模式的要求
  4. 您应该考虑包装OpenGL API。例如,假设您有以下内容:

    // OpenGL type
    struct Vertex
    {
        int SizeInBytes;
    }
    
    public static extern void OpenGLFunction(Vertex[] vertices);
    

    更好的选择是定义自己的界面,然后隐藏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);
        }
    }
    

    (这显然是一个人为的例子,但它应该证明我在代码和其他API之间引入一层抽象方面试图解决的问题)

答案 3 :(得分:4)

.net中值类型的一个未被充分认识的特征是它们可以作为接口约束泛型类型传递而不需要装箱。例如:

T returnSmaller<T>(ref T p1, ref T p2) where T:IComparable<T>
{
  return p1.CompareTo(p2) < 0 ? p1 : p2;
}

请注意,我使用ref参数来消除对这两个参数的额外临时副本; p2的额外副本将在传递给CompareTo方法时最终生成,并且在返回结果时可能至少生成一个额外的副本,但制作两个冗余副本将是比制作​​四个更好。在任何情况下,可以在实现T的任何类型IComparable<T>上无需装箱时调用上述方法。

不幸的是,没有非常好的方式说“如果T是一种类型的结构,将它传递给某种采用该类型的方法;否则如果它是其他类型,则将其传递给方法并将其取出一”。因此,需要特定精确类的代码(如使用API​​的代码)可能必须是非泛型的。尽管如此,如果有一些方法可以在各种结构上使用,那么让这些结构实现接口然后将它们作为约束泛型类型传递可能会带来一些巨大的优势。

答案 4 :(得分:2)

看起来你想要的是一个界面。

public interface IVertex
{
    int SizeInBytes { get; }
    void SetPointers();
}

public struct ColorVertex : IVertex
{
   private Vector3 Position;
   private Vector4 Color;

   public int SizeInBytes
   {
      get { return (3 + 4) * 4; }
   }

   public void SetVertexPointers() // Did you mean SetPointers?
   {
   }
}

接口是有意义的,因为所有方法都被声明为abstract(这意味着它依赖于派生类来实现接口所基本的方法)

答案 5 :(得分:2)

您可以使用接口

interface IVertex 
{
    int SizeInBytes();
    void SetPointers();
}

struct ColorVertex : IVertex
{
   Vector3 Position;
   Vector4 Color;

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

答案 6 :(得分:1)

您可以创建界面IVertex,然后添加到您的结构。

答案 7 :(得分:0)

也许您可以使用"union"类型:

enum VertexType : byte { 
  Vertex,
  ColorVertex
}

[StructLayout(LayoutKind.Explicit)]
struct Vertex {

  [FieldOffset(0)]
  public readonly VertexType Type;

  [FieldOffset(1)]
  public readonly int SizeInBytes;

  public Vertex(VertexType type /* other necessary parameters... */) {
    Type = type;
    // set default values for fields...
    switch (Type) {
      // set the fields for each type:
      case VertexType.ColorVertex: 
        SizeInBytes = (3 + 4) * 4; 
        // other fields ...
        break;
      default: 
        SizeInBytes = 2; // or whatever...
        // other fields ...
        break;
    }
  }

  // other fields with overlapping offsets for the different vertex types...

}

请记住使其不可变并且只访问对每种类型有意义的字段。

答案 8 :(得分:-4)

类也可以是“ 值类型 ”,(在域驱动设计中使用的意义上)。您所要做的就是使其不可变,使构造函数不可公开(受保护或内部),并创建静态工厂方法来创建它们的实例并控制它们的实例化,并且您的属性上没有任何setter ...

注意:此竞赛中的短语 值类型 与值类型与参考类型无关。它与域名Drtiven设计或域建模中使用的 值类型 vs 实体类型 有关...