69

Can structs contain fields of reference types? And if they can is this a bad practice?

4

6 回答 6

101

是的他们可以。这是个好主意吗?嗯,这取决于情况。就个人而言,我很少首先创建自己的结构......我会以一定程度的怀疑态度对待任何新的用户定义结构。我并不是说它总是错误的选择,只是它需要更多的明确论据而不是类。

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

MyValueType foo = ...;
MyValueType bar = foo; // Value type, hence copy...

foo.List.Add("x");
// Eek, bar's list has now changed too!

可变结构是邪恶的。引用可变类型的不可变结构以不同的方式偷偷摸摸地邪恶。

于 2009-06-03T16:26:40.333 回答
19

Sure thing and it's not bad practice to do so.

struct Example {
  public readonly string Field1;
}

The readonly is not necessary but it is good practice to make struct's immutable.

于 2009-06-03T16:22:56.510 回答
7

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

如果您查看 .NET 框架本身,您会发现几乎所有结构都单独包含原始值类型。

于 2009-06-03T16:25:47.870 回答
7

您不能拥有可变结构的原因是引用类型的行为。阅读这篇文章:http ://www.yoda.arachsys.com/csharp/parameters.html

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

如果您绝对必须让结构是可变的,请将任何类实例设置为只读,或者 - 这是不好的做法 - 尽量确保您永远不会复制结构。

于 2011-02-11T14:03:06.830 回答
2

Yes they can.

It depends.

Many hold the stance that a struct should be immutable, and in this case, holding a reference to an object could mean it isn't.

But it depends on the situation.

于 2009-06-03T16:21:15.810 回答
2

由于这得到了反对,我正在尝试重写一点,看看它是否可以变得更清晰。这个问题很老了;但是很好!我最近还遇到了一些详细说明此问题的链接。

我要补充的一点是,如果您确实声明了引用字段,那么您必须能够在自己的块之外进行推理:当有人使用您的结构时。我添加的具体点实际上只是关于声明结构的只读字段;但在这种情况下,结构中的字段可以改变它们的结果;这很难推理。

我遇到了这个链接,程序员在其中声明了一个包含一个字段的类。readonly struct他的类中的字段是一个struct——它是一个LinkedList<T>.Enumerator——并且它破坏了,因为该字段是readonly——他自己的类方法获得了枚举器结构的副本,并且状态被复制而不是动态的。

但是,如果您继续通过简单地readonly从结构字段中删除来修复他的代码(这有效);但是,如果您随后决定创建自己的类 a ,那么struct现在您的结构的使用者不能将其用作只读字段,否则他们反过来会被同样的问题所困扰。(如果这看起来是人为的,因为您没有只读枚举器,那么如果它支持重置,您实际上可能会这样做!)

因此,如果这不是最清楚的例子,我想说的是你可以推理你自己的实现,但如果你是一个结构,你还需要推理复制你的价值的消费者以及他们会得到什么。

我找到的示例链接如下。

他的类不是 a struct,但确实包含 m_Enumerator 字段(并且程序员应该知道它a struct)。

事实证明,此类的方法获得了该值的副本,并且不起作用。--- 你实际上可以非常仔细地检查这个块来理解这一点。

可以通过使字段not readonly来修复它--- 这已经指向混乱。但是您可以通过将字段声明为interface类型来修复它 --- IEnumerator<int>

但是,如果您确实通过将字段声明为 thestruct并且将其声明为 not来修复它readonly然后选择将您的类定义为 a struct,那么现在如果有人将 的实例声明structreadonly某个类中的字段,那么他们输了!

例如:

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

如果你struct用一个interface字段声明 a,你将不知道里面有什么,但是当你简单地引用它时,你实际上可以更多地推断你得到了什么!这很有趣;但仅仅是因为语言允许这么多的自由struct:你需要从非常简单的事情开始;并仅添加您可以具体推理的内容!

还有一点是,该参考资料还说您应该将默认值定义为合理的;这在参考字段中是不可能的!如果您无意中调用了默认构造函数 --- 总是可以使用struct--- 那么您会得到一个空引用。

最后一点也是。许多人捍卫可变结构和大型可变结构。但是,如果您仔细观察,您通常会发现它们只是以一种允许它们对行为进行有限推理的方式来确定这些对象的范围,并且结构不会泄漏到这些不变量可能更改的范围内。

...太多人开始将结构解释为“就像一个类,但是... x、y、z、1、2、alpha、beta disco”。必须将其解释为几个只读值;时期; 除了现在你知道了一些东西,你就可以开始推理添加一些东西了!

我来的例子在这里:

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

于 2017-11-27T14:21:00.933 回答