Есть ли способ определить С# строго типизированные псевдонимы существующих примитивных типов типа `string` или` int`?
Возможно, я демонстрирую свое незнание некоторых часто используемых функций С# или .NET Framework, но я хотел бы знать, есть ли способ, основанный на принципе создания псевдонима типа EmailAddress
, который псевдонимы string
но такое, что я могу расширить его своими собственными методами вроде bool Validate()
?
Я знаю псевдонимы using x = Some.Type;
, но они не являются глобальными и не обеспечивают безопасность типа, т.е. можно заменить обычную string
для псевдонима использования в текущем файле. Я бы хотел, чтобы мой EmailAddress
был его собственным типом, независимым и не взаимозаменяемым с типом string
, который он тениет.
Мое текущее решение состоит в том, чтобы генерировать классы public sealed partial EmailAddress : IEquatable<EmailAddress>, IXmlSerializable
с шаблоном T4, генерирующим операторы преобразования неявных строк шаблона и другие подобные вещи. На данный момент это хорошо для меня и дает мне большую гибкость, но в глубине моего сознания кажется глупым, что я должен генерировать такое обильное количество кода шаблона, чтобы сделать что-то так же просто, как создать сильный псевдоним типа.
Возможно, это невозможно, кроме как с генерацией кода, но мне любопытно, если другие пытались что-то подобное с их дизайнами и какими были ваши впечатления. Если ничего другого, возможно, это может послужить хорошим вариантом использования для такой функции псевдонима в гипотетической будущей версии С#. Спасибо!
EDIT: реальное значение, которое я хочу из этого, состоит в том, чтобы иметь возможность получить безопасность типов с примитивными типами, которые представляют разные типы/форматы данных. Например, EmailAddress
и SocialSecurityNumber
и PhoneNumber
, все из которых используют string
как их базовый тип, но не являются взаимозаменяемыми типами сами по себе. Я думаю, что это дает вам гораздо более читаемый и самодокументирующий код, не говоря уже о преимуществах более сложных возможностей перегрузки методов, которые менее двусмысленны.
Ответы
Ответ 1
Если вы посмотрите на .NET Framework System.Uri, это самый близкий пример, похожий на адрес электронной почты. В .NET шаблон должен обернуть что-то в класс и добавить ограничения таким образом.
Добавление сильной типизации, которая добавляет дополнительные ограничения для простых типов, - интересная языковая функция, которая, как мне кажется, имеет некоторый функциональный язык. Я не могу вспомнить название языка, которое позволит вам добавлять размерные единицы, такие как ноги, к вашим значениям и выполнять анализ размеров в ваших уравнениях, чтобы гарантировать соответствие единиц.
Ответ 2
Некоторые предпосылки о том, почему string
запечатываются:
От http://www.code-magazine.com/Article.aspx?quickid=0501091:
Рори: Эй, Джей, ты не возражаешь, если я попрошу тебя пару вопросов? Мне уже любопытно о некоторых вещах. Прежде всего, и это было поднято в одном из MSDN события, которые я сделал на этой неделе, почему String запечатаны? Примечание: для программистов VB.NET, Запечатанный = NotInheritable.
Джей: Потому что мы делаем много волшебства трюки в String, чтобы попытаться убедиться мы можем оптимизировать такие вещи, как сравнений, чтобы сделать их так же быстро, как мы, возможно, можем. Итак, мы крадем бит от указателей и других вещей там, чтобы отметить вещи. Просто дайте вам пример, и я не знал это когда я начал, но если строка имеет дефис или апостроф в нем [тогда] он выглядит иначе, чем если бы он просто есть текст в нем, а алгоритм для сортировки, если у вас есть дефис или апостроф, если вы делаете глобально-сортировка сортировки довольно сложный, поэтому мы фактически отмечаем независимо от того, имеет ли строка тип поведения в нем.
Рори: Итак, вы говорите, что в мире струн, если вы не печать String было бы много места для разрушения большого количества хаоса, если люди пытались подклассифицировать его.
Джей: Точно. Это изменило бы весь макет объекта, поэтому мы не смогли бы сыграть трюки что мы играем с этой скоростью.
Вот статья CodeProject, которую вы, вероятно, видели раньше:
http://www.codeproject.com/KB/cs/expandSealed.aspx
Итак, неявный оператор - ваше единственное решение.
Ответ 3
Соответствует ли класс System.Net.Mail.MailAddress
вашим потребностям или, по крайней мере, "помогает"?
EDIT: он явно не IEquatable или ISerializable, но вы можете легко добавить их в свою собственную оболочку.
Ответ 4
Кажется, у вас есть хотя бы разумный С# knoledgde, поэтому мой ответ может показаться глупым, но то, что вы хотите, называется "иерархией типов", а ребята, которые кодировали класс String, хотели помешать вам использовать эту функцию "OO", поэтому они сделали класс String запечатанным, поэтому вы не сможете делать то, что хотите. Лучший подход - это вы сейчас: сделайте свой собственный тип и неявное преобразование в String.
Ответ 5
Я думаю, я не понимаю, почему вы хотите иметь как сильные типы, так и неявное преобразование строк одновременно. Для меня один исключает другого.
Я попытался решить ту же проблему для ints (вы упоминаете int в заголовке, но не в вопросе). Я обнаружил, что объявление перечисления дает вам целочисленное целое число, которое должно быть явно отображено из/в int.
Update
Перечисления не могут быть предназначены для открытых наборов, но все равно могут использоваться таким образом. Этот пример из эксперимента по компиляции, чтобы различать столбцы идентификаторов нескольких таблиц в базе данных:
enum ProcID { Unassigned = 0 }
enum TenderID { Unassigned = 0 }
void Test()
{
ProcID p = 0;
TenderID t = 0; <-- 0 is assignable to every enum
p = (ProcID)3; <-- need to explicitly convert
if (p == t) <-- operator == cannot be applied
t = -1; <-- cannot implicitly convert
DoProc(p);
DoProc(t); <-- no overloaded method found
DoTender(t);
}
void DoProc(ProcID p)
{
}
void DoTender(TenderID t)
{
}
Ответ 6
Я думаю, вы хотите использовать методы расширения. Они позволяют расширять функциональность классов без создания нового производного типа.
Ответ 7
Я сделал этот класс для удовлетворения одинаковых потребностей. Это для типа "int" (у меня также есть один для "string" ):
public class NamedInt : IComparable<int>, IEquatable<int>
{
internal int Value { get; }
protected NamedInt() { }
protected NamedInt(int val) { Value = val; }
protected NamedInt(string val) { Value = Convert.ToInt32(val); }
public static implicit operator int (NamedInt val) { return val.Value; }
public static bool operator ==(NamedInt a, int b) { return a?.Value == b; }
public static bool operator ==(NamedInt a, NamedInt b) { return a?.Value == b?.Value; }
public static bool operator !=(NamedInt a, int b) { return !(a==b); }
public static bool operator !=(NamedInt a, NamedInt b) { return !(a==b); }
public bool Equals(int other) { return Equals(new NamedInt(other)); }
public override bool Equals(object other) {
if ((other.GetType() != GetType() && other.GetType() != typeof(string))) return false;
return Equals(new NamedInt(other.ToString()));
}
private bool Equals(NamedInt other) {
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Equals(Value, other.Value);
}
public int CompareTo(int other) { return Value - other; }
public int CompareTo(NamedInt other) { return Value - other.Value; }
public override int GetHashCode() { return Value.GetHashCode(); }
public override string ToString() { return Value.ToString(); }
}
И использовать его в вашем случае:
public class MyStronglyTypedInt: NamedInt {
public MyStronglyTypedInt(int value) : base(value) {
// Your validation can go here
}
public static implicit operator MyStronglyTypedInt(int value) {
return new MyStronglyTypedInt(value);
}
public bool Validate() {
// Your validation can go here
}
}
Если вам нужно его сериализовать (Newtonsoft.Json), дайте мне знать, и я добавлю код.