2011-06-17 46 views
2

我需要封装一个固定的用户定义值类型的数组(我们称之为结构2)在另一个结构(结构1)内,但固定的数组只能为原生值类型声明。所以我想通过定义[]运算符来创建第三个结构(结构包装器),它必须作为struct2的数组工作。 所以结构包装值类型,并提供索引访问

struct Struct2 
    { 
     int a; 
     int b 
    } 

    struct wrapper 
     { 
      Struct2 str0; 
      Struct2 str1; 
      public Struct2 this[int index] 
      { 
       get 
       { 
        switch (index) 
        { 
         case 0: return str0; 
         case 1: return str1; 
         default: return str0; 
        } 
       } 
      } 
     } 

     static void Main() 
     { 
      wrapper wr = new wrapper(); 
      wr[0].a = 123; // i would like to do this but this doesnt work 
      //but if we do like this 
      Struct2 [] arr = new Struct2[5]; 
      arr[0].a = 123 ;// this works , we are effectively modifying the object    
      //contained in the array (not just a copy) 
     } 

好吧,这个代码不工作,因为Struct2是值类型,当操作员返回然后返回包含在它的副本,而不是实际的对象STR0。我想使用索引器访问该字段!可能吗 ?为什么使用Array类可能? 我知道这可能通过返回一个指针,但这涉及到在运算符定义中使用fixed关键字,我想避免这种情况,因为需要广泛访问'数组',并且我最终将使用固定语句2次(内部和外部保持地址不变)。另外我已经考虑过使用指针,只需在Struct1中声明N adiacent Struct2字段并使用指向第一个的指针作为数组,但我更愿意使用包装器来模拟数组行为。 Agai,这可能吗?

编辑 看来,它是不可能实现与值类型一起工作的自定义数组(如普通数组一样)。 顺便说一句,我能找到的最近的解决方案是这样的,但正如我写的,我希望避免指针。

struct Struct2 
    { 
     int a; 
     int b 
    } 
    struct Struct1 
    { 
     public Struct2 * str2arr ; //this can be avoided 
     public Struct2 str0; 
     //wrapper is not needed anymore as Struct1 is wrapping the Struct2 array by itself 
     //and a pointer is used for indexing operations 
     //struct layout needs to be sequential 
     private Struct2 str1; 
     private Struct2 str2; 
     private Struct2 str3; 
     private Struct2 str4; 


    } 
    static void Main() 
    { 
    Struct1 myStruct = new Struct1(); 
    fixed(myStruct.str2arr = &myStruct.str0) 
    { 
     myStruct.str2arr[1] = 123; 
    } 
    } 
+0

1.数组是一个引用类型...当您使用索引器时,如果它是一个值类型,您也会获得内容的副本。 2.这个问题背后的要求是什么? – 2011-06-17 06:39:14

+0

你确定吗?我认为你的观点1至少有一半是错误的,我要编辑这个问题并加以澄清。 – N4rk0 2011-06-17 07:10:36

+0

代码示例中的struct1在哪里? – Yhrn 2011-06-17 07:57:01

回答

1

如果没有至少一个包含您的数据的根类,就无法实现您想要的目标。这里是你的问题的一个简装版本的作品,即能够您的伪数组访问您Struct2领域a,如解决方案:

wrapper wr = new wrapper(); // needs to be a class 
wr[0].a = 123; 
wr[1].a = 456; 

System.Console.WriteLine ("wr[0].a = {0}", wr[0].a); // displays 123 
System.Console.WriteLine ("wr[1].a = {0}", wr[1].a); // displays 456 

你的包装必须返回引用类型,如果你希望能够修改它的内容,否则当你访问结构体时,你总会碰到发生值类型复制的情况。但是你的包装器仍然可以将内部数据存储为一系列结构。

这里是我的解决方案:

struct Struct2 
{ 
    public int a; 
} 

class wrapper // sorry, cannot use 'struct' here ... 
{ 
    Struct2 str0; 
    Struct2 str1; 

    public helper this[int index] 
    { 
     get 
     { 
      return new helper (this, index); 
     } 
    } 

    int GetValueA(int index) 
    { 
     switch (index) 
     { 
      case 0: return str0.a; 
      case 1: return str1.a; 
      default: throw new System.IndexOutOfRangeException(); 
     } 
    } 

    void SetValueA(int index, int value) 
    { 
     switch (index) 
     { 
      case 0: str0.a = value; break; 
      case 1: str1.a = value; break; 
     } 
    } 

    public class helper 
    { 
     public helper(wrapper host, int index) 
     { 
      this.host = host; 
      this.index = index; 
     } 

     public int a 
     { 
      get { return this.host.GetValueA (index); } 
      set { this.host.SetValueA (index, value); } 
     } 

     private readonly wrapper host; 
     private readonly int index; 
    } 
} 

当你的担心似乎是速度,那么没有包装将让你快乐。我会重新考虑整个问题,如果可能的话,写一个类来管理你的数据结构。

如果您的所有数据都可以表示为int,也许您应该考虑使用大量整数,然后添加访问该中央数组以找到要操纵的字段的类,方法是索引到适当的项目中。

class Wrapper 
{ 
    ... 

    int[] data; 

    public StructWrapper1 this[int index] 
    { 
     get 
     { 
      return new StructWrapper1 (this, index); 
     } 
    } 

    public class StructWrapper1 
    { 
     public StructWrapper1(Wrapper wrapper, int index) 
     { 
      this.wrapper = wrapper; 
      this.index = index; 
     } 
     public int A 
     { 
      get { return this.wrapper[this.index*2+0]; } 
      set { this.wrapper[this.index*2+0] = value; } 
     } 
     public int B 
     { 
      get { return this.wrapper[this.index*2+1]; } 
      set { this.wrapper[this.index*2+1] = value; } 
     } 

     private readonly Wrapper wrapper; 
     private readonly int index; 
    } 
} 

如果您需要表示各种数据类型,则可以考虑对每种字段类型使用一个数组。

+0

我认为可能有一种方法,因为Array类的[]运算符不会执行该副本(如果您访问数组中包含的值类型的成员),那么我想模仿这种行为,但看起来这种语言不允许。 – N4rk0 2011-06-17 08:22:27

+0

@ N4kr0:确实,这是编译器的特权,可以做你不能实现的东西;-) – 2011-06-17 09:50:34

1

结构被称为value semantics这就是为什么你不能直接修改值。

使用类,有可能两个变量引用同一个对象,因此可能对一个变量的操作影响另一个变量引用的对象。使用结构体时,每个变量都有自己的数据副本(除了ref和out参数变量外),并且一个操作不可能影响另一个。

你应该考虑使用类,而不是结构。

编辑 如果您确实需要将Struct2作为结构体,那么您可以将包装器创建为类。在课堂上,您可以使用固定的结构数组并通过方法进行修改。

class wrapper 
{ 
    Struct2[] structArray = new Struct2[10]; 
    public void setValues(int index, int a, int b) 
    { 
     structArray[index].a = a; 
     structArray[index].b = b; 
    } 
} 

wrapper wr = new wrapper(); 
wr.setValues(0, 2, 3); 
+0

我已经知道......如果我可以使用类,我会用它们。如果这是不可能的,那么你如何使用类Array获得我想要的行为?看看这个问题的澄清。 – N4rk0 2011-06-17 07:07:34

1

你不能做你想要的,因为Struct2是一个值类型。编译器会给你一个错误cannot modify return value because it is not a variable.,因为允许你做wr[0]==123至少会让你感到困惑,因为你会修改存储在array中的值的一个副本,这将被丢弃。

我想你应该在你的方法中重新考虑很多事情。首先,不要使用可变值类型,它们只会导致问题。在价值类型方面,不变性是要走的路。其次,如果你需要可变性和引用语义,那么为什么要使用struct呢?考虑改用class

否则,最接近您正在寻找的功能的是,在您的包装类中将基础Struct2数组暴露为readonly字段。

public struct Wrapper(...) 
{ 
    public readonly Struct2[] Arr; 
} 

不,你可以这样做:

wr.Arr[0] = 123; 

但是,当然,你也可以这样做:

wr.Arr[0] = new Struct2(...) 

这可能是一个问题。如果可能的话,你可以通过使Strutc2构造函数内部回避。

+0

这就是速度,我需要创建很多这些对象,并且必须广泛访问它们。 顺便说一句,我对这个问题所做的编辑,我显示了一种方式是ARR [0] = 123修改实际的对象。 – N4rk0 2011-06-17 07:17:45

+0

我没有办法看到你可以干净地做你想做的事。考虑将可变方法重构为可直接与底层数组进行交互的包装器结构。否则将底层数组作为'readonly'字段。 – InBetween 2011-06-17 07:21:12

+0

我不想阻止写入数组,但我想实现一个固定的数组,这意味着它驻留在结构内存中,它不是一个真正的数组(类),而是一个简单的缓冲区。如果我会用类数组我会刚刚在Struct1中声明它,而不需要任何包装。但我需要Struct1只包含值类型,同样适用于Struct2,所以我不能使用数组,至于引用类型。 – N4rk0 2011-06-17 10:15:41

1

我认为你需要放弃这里的索引器。即使在用户定义的类型和数组上使用它时看起来是一样的,但它不是。当你定义一个索引器getter时,它只是一个Get(int index)方法的语法糖,当你从一个方法返回一个值类型时,它会返回值,这就是值类型的整个点。

例如:

struct wrapper { 
    public Struct2 str0; 
    public Struct2 str1 { get; set; } 
    public Struct2 this[int index] { 
     get { 
      switch (index) { 
        case 1: return str1; 
        default: return str0; 
      } 
     } 
    } 
} 

static void Main(string[] args) { 
    wrapper wr = new wrapper(); 
    wr.str0.a = 123; // Works, accessing fields only. 
    wr.str1.a = 123; // Does not work, str1 is a property, which is really a method 
    wr[0].a = 123; // Also does not work 

} 

我不能拿出做你想要用不会造成任何中间引用类型实例的索引什么的任何方式。所以这可能会留下创建方法来设置基于索引的内部结构的值(由Renius建议)。