如何在C#中的结构上强制使用工厂

时间:2008-12-04 20:33:41

标签: c# struct factory

我有一个c#struct,我需要禁止在其上调用no args构造函数。

MyStruct a;
/// init a by members   // OK
MyStruct b = MyStruct.Fact(args); // OK, inits by memebers

MyStruct s = new MyStruct(); // can't have that

我这样做主要是为所有成员强制explicet值,因为没有有效的默认值,并且所有成员都必须具有有效值。

在C ++中,这很容易,添加一个私有构造函数,但c#不允许这样做。

有没有办法防止上述情况?

我真的需要强制使用工厂,因此阻止所有公共构造函数调用也会起作用。


完整披露:为了避免单声道依赖,c#app会被自动转换为D,其中new Struct()会产生一个指针,这对我来说很糟糕。然而,这个问题是相关的,尽管如此,请忽略它。

5 个答案:

答案 0 :(得分:11)

你做不到。根据C#中的定义,所有结构都有一个公共无参数构造函数。 (在CLR中,它们几乎都没有,但是它们总是可以像它们一样。)请参阅this question,了解为什么不能在结构上定义自己的无参数构造函数(无论如何都在C#中)。

事实上,如果您愿意在IL中编写值类型,可以阻止此语句。我刚刚检查过,如果你确定你的值类型只有一个无参数构造函数,并将其设置为内部,那么你将无法编写MyStruct ms = new MyStruct();但这不会阻止:

MyStruct[] array = new MyStruct[1];
MyStruct ms = array[0];

围绕新的限制 - 所以它并没有真正为你买任何东西。这真的很不错,因为在IL中搞乱会很混乱。

你确定你真的想要首先编写一个结构吗?那几乎从来都不是一个好主意。

答案 1 :(得分:3)

你不能。

结构中的所有值必须在构造时初始化,并且在构造函数之外无法做到这一点。

你究竟想通过这样做完成什么?结构是值类型,因此您将获得大多数操作的“新”结构。要在结构上使用工厂的各种约束将非常困难。

答案 2 :(得分:2)

任何人都可以在不调用构造函数的情况下随时创建结构,只要他们可以访问结构。以这种方式思考:

如果创建一个包含1000个元素的对象数组,它们都会被初始化为null,因此不会调用任何构造函数。

对于结构体,没有null这样的东西。如果创建一个包含1000个DateTime对象的数组,则它们都初始化为零,等于DateTime.Min。运行时的设计者选择创建它,这样你就可以创建一个结构数组,而不需要调用构造函数N次 - 这是许多人都没有意识到的性能损失。

但是你的工厂理念很好。它是否满足您创建界面并公开它的需要,但是使结构变为私有或内部?那就差不多了。

答案 3 :(得分:0)

将它放在自己的程序集中,并将没有args的MyStruct()作为内部(VB中的朋友)。使工厂与MyStruct()具有相同的程序集,但具有公共访问者。

现在工厂可以访问no args MyStruct,但是从程序集外部调用的任何东西都必须使用Factory。

编辑:我的不好,我没有考虑到这是一个结构。你不能用一个结构来做这个,只能用一个类 - 在这种情况下,我之前的陈述就是这样。

答案 4 :(得分:0)

您可以创建一个结构来检测它是否处于默认的初始化状态,然后在这种情况下执行适当的操作。我离开了工厂,但是在一般的简单案例中,构造函数也可以是一个适当的工厂。

这是很多样板代码。既然你使用D,你可能会想到同样的事情,“我希望C#有模板混合。”

示例:

using System;

namespace CrazyStruct
{
    public struct MyStruct
    {
        private readonly int _height;
        private readonly bool _init; // Will be 'false' using default(MyStruct).

        /// <summary>
        /// Height in centimeters.
        /// </summary>
        public int Height
        {
            get
            {
                if (!_init)
                {
                    // Alternatively, could have the preferred default value set here.
                    // _height = 200; // cm
                    // _heightInit = true;
                    throw new InvalidOperationException("Height has not been initialized.");
                }
                return _height;
            }
            // No set:  immutable-ish.
        }

        private MyStruct(int height)
        {
            _height = height;
            _init = true;
        }

        public static MyStruct Factory(int height)
        {
            return new MyStruct(height);
        }
    }

    static class Program
    {
        static void Main(string[] args)
        {
            MyStruct my = MyStruct.Factory(195);
            Console.WriteLine("My height is {0} cm.", my.Height);
            try
            {
                var array = new MyStruct[1];
                var ms = array[0];
                Console.WriteLine("My height is not {0} cm.", ms.Height);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Caught the expected exception: {0}.", ex);
            }
            Console.ReadKey();
        }
    }
}