Struct包装值类型并使用索引提供访问权限

时间:2011-06-17 05:32:51

标签: c# .net

我需要在另一个struct(struct1)中封装一个用户定义的值类型的固定数组(让我们称之为struct2),但是只能为本机值类型声明固定数组。所以我想创建一个第三个结构(struct wrapper),它必须通过定义[]运算符作为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关键字,我想避免这种情况,因为'数组'需要被广泛访问,我最终会使用fixed语句2次(内部和外部保持地址固定)。此外,我已经考虑使用指针,只是在Struct1中声明N个adiacent Struct2字段并使用指向第一个的指针作为数组,但我更喜欢使用包装器来模拟数组行为。 阿盖,有可能吗?

编辑 似乎不可能实现一个使用值类型的自定义数组(如常见数组那样)。 顺便说一下,我能找到的最近的解决方案就是这个,但正如我所写,我本来希望避免使用指针。

    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;
     }
    }  

4 个答案:

答案 0 :(得分:1)

如果没有至少包含数据的根类,就无法实现您的目标。这是一个适用于您的问题的精简版本的解决方案,即能够访问伪数组中a的字段Struct2,例如:

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;
    }
}

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

答案 1 :(得分:1)

Struct被称为value semantics,这就是为什么你不能直接修改值的原因。

  

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

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

EDIT 如果你真的需要将Struct2作为struct,那么你可以创建包装器作为类。在类中,您可以拥有固定的结构数组并通过方法对其进行修改。

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);

答案 2 :(得分: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构造函数来避免这种情况。

答案 3 :(得分: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所建议的那样)。