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

本文关键字:访问 访问权 权限 索引 包装 类型 结构 | 更新日期: 2023-09-27 17:59:17

我需要在其他结构(struct1)中封装一个用户定义的值类型的固定数组(让我们称之为struct2),但固定数组只能为本机值类型声明。所以我想创建第三个结构(结构包装器),它必须通过定义[]运算符作为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语句两次(内部和外部以保持地址固定)。此外,我已经考虑过使用指针,只需在Struct1中声明N个绝对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;
     }
    }  

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

因为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构造函数设置为内部构造函数来避免这种情况。

结构被称为值语义,这就是为什么不能直接修改值的原因。

有了类,两个变量可以引用同一个对象,因此对一个变量的操作可能会影响另一个变量引用的对象。对于structs,每个变量都有自己的数据副本(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);

如果没有至少一个包含数据的根类,就无法实现您想要做的事情。这里有一个适用于问题的精简版本的解决方案,即能够访问伪数组中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

如果你想修改它的内容,你的包装器必须返回一个引用类型,否则你总是会碰到访问structs时发生的值类型复制。但是您的包装器可能仍然将其数据作为一系列结构存储在内部。

这是我的解决方案:

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

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

我认为您需要放弃这里的索引器。尽管在用户定义类型的实例和数组上使用它时看起来是一样的,但事实并非如此。当你定义一个索引器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所建议的)。