代码之家  ›  专栏  ›  技术社区  ›  James Hay

结构不能包含引用类型的字段

  •  49
  • James Hay  · 技术社区  · 16 年前

    结构能否包含引用类型的字段?如果可以的话,这是一个坏习惯吗?

    6 回复  |  直到 7 年前
        1
  •  77
  •   Jon Skeet    16 年前

    是的,他们能。这是个好主意吗?嗯,那要看情况而定。就个人而言,我很少在一开始就创建自己的结构…对于任何新的用户定义结构,我都会有一定程度的怀疑。我不是说 总是 错误的选项,只是它需要一个比类更清晰的参数。

    但是,对于结构来说,引用可变对象是一个坏主意…否则,可以有两个值, 独立但不是:

    MyValueType foo = ...;
    MyValueType bar = foo; // Value type, hence copy...
    
    foo.List.Add("x");
    // Eek, bar's list has now changed too!
    

    可变结构是邪恶的。引用可变类型的不可变结构以不同的方式暗中邪恶。

        2
  •  18
  •   JaredPar    16 年前

    当然,这样做也不错。

    struct Example {
      public readonly string Field1;
    }
    

    readonly不是必需的,但最好使结构不可变。

        3
  •  4
  •   Judah Gabriel Himango    16 年前

    是的,这是可能的,而且是的,这通常是一种不好的做法。

    如果您查看.NET框架本身,您将看到几乎所有结构都只包含基元值类型。

        4
  •  2
  •   Lasse V. Karlsen    16 年前

    是的,他们能。

    这要看情况而定。

    许多人认为结构应该是不可变的,在这种情况下,持有对对象的引用可能意味着它不是。

    但这要视情况而定。

        5
  •  2
  •   Matthew    14 年前

    不能使用可变结构的原因是引用类型的行为。阅读本文: http://www.yoda.arachsys.com/csharp/parameters.html

    当您有一个包含一个对象的结构(任何不是像int或double这样的基元的东西)并且您复制了该结构的一个实例时,里面的对象不会被“深度”复制,因为它只是一个指向包含实际类的内存位置的引用(指针)。因此,如果复制包含类实例的可变结构,则副本将引用与原始结构相同的实例(因此,上面更改了bar的列表)。

    如果您必须使结构是可变的,请将其中的任何类实例设置为只读的,或者(这是一种糟糕的做法)尝试确保您从不复制该结构。

        6
  •  1
  •   Steven Coco    7 年前

    既然这次投票被否决了,我想重写一下,看看是否能变得更清楚。这个问题很古老,但很好!我最近也遇到了一些详细阐述这一点的链接。

    我想补充的一点是如果你 声明引用字段,您必须能够 外部 当某人 使用 你的结构。我添加的特定点实际上只是关于声明一个结构的只读字段;但是在这种情况下,您的结构中的字段可以更改它们的结果;这很难解释。

    我看到了这个链接,程序员在这里声明了 包含一个 readonly struct 字段。这个 领域 在他的班级里是一个 结构 这是一个 LinkedList<T>.Enumerator --它坏了,因为田野 只读 ---他自己的类方法获取枚举器结构的副本,状态是复制的,而不是动态的。

    但是 ,如果您只需删除 只读 从结构字段(其中 作品 ; 然而,后来 如果你决定让自己的班级成为A班 结构 现在 再一次 消费者 你的 结构不能将其用作只读字段,否则它们会被同一问题咬到。(如果这看起来是人为的,因为你没有一个只读的枚举器,你实际上 可以 如果它支持重置!)

    因此,如果这不是最清楚的例子,我要说的是,你可以对自己的实现进行推理,但是如果你是一个结构,你需要 消费者模仿你的价值以及他们将得到什么的原因。

    我发现的示例链接在下面。

    他的班级不是 结构 ,但是包含m_枚举器字段(并且程序员应该知道它 结构 )

    结果是,这个类的方法得到了该值的一个副本,但不起作用。---你可以实际检查 要理解这一点,必须非常小心地封锁。

    可以 把它弄好 只读 ——这已经让人困惑了。但是你可以 通过将字段声明为 interface 类型--- IEnumerator<int> .

    但是 如果你 通过保留声明为 结构 并且不声明 只读 , 然后选择将类定义为 结构 然后 现在 如果有人声明 你的 结构 作为一个 只读 在某个班级里, 再一次 他们输了!

    例如。:

    public class Program
    {
        private struct EnumeratorWrapper : IEnumerator<int>
        {
            // Fails always --- the local methods read the readonly struct and get a copy
            //private readonly LinkedList<int>.Enumerator m_Enumerator;
    
            // Fixes one: --- locally, methods no longer get a copy;
            // BUT if a consumer of THIS struct makes a readonly field, then again they will
            // always get a copy of THIS, AND this contains a copy of this struct field!
            private LinkedList<int>.Enumerator m_Enumerator;
    
            // Fixes both!!
            // Because this is not a value type, even a consumer of THIS struct making a
            // readonly copy, always reads the memory pointer and not a value
            //private IEnumerator<int> m_Enumerator;
    
    
            public EnumeratorWrapper(LinkedList<int> linkedList) 
                => m_Enumerator = linkedList.GetEnumerator();
    
    
            public int Current
                => m_Enumerator.Current;
    
            object System.Collections.IEnumerator.Current
                => Current;
    
            public bool MoveNext()
                => m_Enumerator.MoveNext();
    
            public void Reset()
                => ((System.Collections.IEnumerator) m_Enumerator).Reset();
    
            public void Dispose()
                => m_Enumerator.Dispose();
        }
    
    
        private readonly LinkedList<int> l = new LinkedList<int>();
        private readonly EnumeratorWrapper e;
    
    
        public Program()
        {
            for (int i = 0; i < 10; ++i) {
                l.AddLast(i);
            }
            e = new EnumeratorWrapper(l);
        }
    
    
        public static void Main()
        {
            Program p = new Program();
    
            // This works --- a local copy every time
            EnumeratorWrapper e = new EnumeratorWrapper(p.l);
            while (e.MoveNext()) {
                Console.WriteLine(e.Current);
            }
    
            // This fails if the struct cannot support living in a readonly field
            while (p.e.MoveNext()) {
                Console.WriteLine(p.e.Current);
            }
            Console.ReadKey();
        }
    }
    

    如果您声明 结构 用一个 界面 菲尔德,你不会知道你在那里有什么,但实际上,当你简单地引用它时,你可以对你得到的东西有更多的理由!这很有趣,但仅仅是因为语言允许如此多的自由 结构 :你需要从一些非常简单的事情开始;并且只添加你能具体解释的内容!

    还有一点是参考资料上也说你 应该 将默认值定义为合理的;即 可以使用参考字段!如果不小心调用了默认的构造函数,则 结构 ---然后得到一个空引用。

    最后一个音符。许多人保护可变结构和大型可变结构。但是如果您仔细观察,您通常会发现它们只是以一种允许它们对行为进行有限推理的方式来限定这些对象,并且结构不会泄漏到那些不变量所在的范围中。 能够 改变。

    …太多人开始将结构解释为“就像一个类,但是……x,y,z,1,2,alpha,beta迪斯科”。它必须解释为几个只读值;句点;除非现在您知道了一些东西,否则您可以开始考虑添加一些东西!

    我来阿克罗斯的例子如下:

    https://www.red-gate.com/simple-talk/blogs/why-enumerator-structs-are-a-really-bad-idea/