Как обеспечить использование фабрики в структуре в C#
Вопрос
У меня есть структура С#, где мне нужно запретить вызов конструктора без аргументов.
MyStruct a;
/// init a by members // OK
MyStruct b = MyStruct.Fact(args); // OK, inits by memebers
MyStruct s = new MyStruct(); // can't have that
Я делаю это главным образом для того, чтобы принудительно использовать явные значения для всех членов, поскольку не существует допустимых значений по умолчанию, и все члены должны иметь действительные значения.
В C++ это было бы легко, добавьте частный конструктор, но C# этого не позволяет.
Есть ли способ предотвратить вышеописанное?
Мне действительно нужно принудительно использовать фабрику, чтобы запрет всех вызовов общедоступных конструкторов работал так же хорошо.
Полное раскрытие:чтобы избежать монозависимости, приложение С# автоматически переводится на D, где new Struct()
приводит к указателю, и это все портит для меня.Однако, несмотря на это, этот вопрос актуален, поэтому просто игнорируйте его.
Решение
Ты не можешь. Все структуры имеют открытый конструктор без параметров по определению в C #. (В CLR почти никто из них этого не делает, но они всегда могут действовать так, как будто они это делают.) См. этот вопрос , чтобы узнать, почему не определяйте свои собственные беспараметрические конструкторы для структур (в любом случае, в C #).
На самом деле, вы можете запретить это утверждение, если вы счастливы написать свой тип значения в IL. Я только что проверил, и если вы убедитесь, что ваш тип значения имеет только конструктор без параметров, и сделаете его внутренним, вы не сможете писать MyStruct ms = new MyStruct();
, но это не мешает:
MyStruct[] array = new MyStruct[1];
MyStruct ms = array[0];
, который работает вокруг нового ограничения - так что он на самом деле ничего не покупает. Это действительно хорошо, так как возиться в IL было бы грязно.
Вы уверены, что действительно хотите писать структуру во-первых? Это почти никогда не хорошая идея.
Другие советы
Вы не можете.
Все значения в структуре должны быть инициализированы во время конструирования, и нет способа сделать это вне конструктора. Р>
Что именно вы пытаетесь достичь, делая это? Структуры являются типами значений, поэтому вы получите & Quot; new & Quot; структура для большинства операций. Будет очень трудно реализовать ограничения, для которых вы будете использовать фабрику в структуре.
Каждый может создать структуру в любое время, не вызывая конструктор, если у него есть доступ к структуре. Подумайте об этом так:
Если вы создаете массив объектов с 1000 элементами, все они инициализируются нулем, поэтому конструкторы не вызываются.
В структурах нет такой вещи, как нуль. Если вы создаете массив с 1000 объектами DateTime, все они инициализируются нулем, равным DateTime.Min. Дизайнеры среды выполнения решили сделать это так, чтобы вы могли создавать массив структур, не вызывая конструктор N раз - это был хит производительности, о котором многие даже не подозревали.
Однако ваша заводская идея хороша. Будет ли это соответствовать вашим потребностям - создать интерфейс и показать его, но сделать структуру частной или внутренней? Это примерно так близко, как вы получите.
Поместите его в свою сборку и используйте MyStruct () без аргументов в качестве Internal (Friend in VB). Фабрика должна быть в той же сборке, что и MyStruct (), но с открытым доступом.
Теперь фабрика может получить доступ к MyStruct без аргументов, но все, что вызывает извне сборки, должно использовать Factory.
Редактировать: плохо, я не учел, что это структура. Вы не можете сделать это со структурой, только с классом - в этом случае мое предыдущее утверждение остается в силе.
Вы можете создать структуру, которая определяет, находится ли она в инициализированном состоянии по умолчанию, а затем сделать что-то подходящее в этом случае.Я оставил Фабрику, но в обычном, простом случае конструктор также может быть адекватной фабрикой.
Это много шаблонного кода.Поскольку вы используете 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();
}
}
}