Тут є дві проблеми: 1) тестування, щоб перевірити, чи є тип нерегульованим; і 2) тестування на предмет того, чи представляє об'єкт нульовий тип.
Для випуску 1 (тестування типу) ось рішення, яке я використовував у своїх власних системах: TypeIsNullable-check рішення
Для випуску 2 (тестування об'єкта) рішення Діна Крейда вище працює для типів значень, але він не працює для референтних типів, оскільки при використанні перевантаження <T> завжди повертається помилковим. Оскільки типи посилань по суті є нульовими, тестування референсного типу завжди повинно повернути істину. Будь ласка, дивіться примітку [Про "зведеність"] нижче для пояснення цих семантик. Отже, ось моя модифікація підходу Діна:
public static bool IsObjectNullable<T>(T obj)
{
// If the parameter-Type is a reference type, or if the parameter is null, then the object is always nullable
if (!typeof(T).IsValueType || obj == null)
return true;
// Since the object passed is a ValueType, and it is not null, it cannot be a nullable object
return false;
}
public static bool IsObjectNullable<T>(T? obj) where T : struct
{
// Always return true, since the object-type passed is guaranteed by the compiler to always be nullable
return true;
}
І ось моя модифікація клієнтського тестового коду для вищезазначеного рішення:
int a = 123;
int? b = null;
object c = new object();
object d = null;
int? e = 456;
var f = (int?)789;
string g = "something";
bool isnullable = IsObjectNullable(a); // false
isnullable = IsObjectNullable(b); // true
isnullable = IsObjectNullable(c); // true
isnullable = IsObjectNullable(d); // true
isnullable = IsObjectNullable(e); // true
isnullable = IsObjectNullable(f); // true
isnullable = IsObjectNullable(g); // true
Причина, що я змінив підхід Діна в IsObjectNullable <T> (T t), полягає в тому, що його оригінальний підхід завжди повертав помилковим для еталонного типу. Оскільки такий метод, як IsObjectNullable, повинен вміти обробляти значення опорного типу і оскільки всі типи посилань притаманні нульовому значенню, то якщо передається або тип посилання, або нуль, метод завжди повинен повертати true.
Вищевказані два способи можна було б замінити наступним єдиним методом та досягти однакового результату:
public static bool IsObjectNullable<T>(T obj)
{
Type argType = typeof(T);
if (!argType.IsValueType || obj == null)
return true;
return argType.IsGenericType && argType.GetGenericTypeDefinition() == typeof(Nullable<>);
}
Однак проблема цього останнього одно методичного підходу полягає в тому, що продуктивність страждає при використанні параметра Nullable <T>. На виконання останнього рядка цього єдиного методу потрібно набагато більше часу, ніж це дозволяє дозволити компілятору вибрати другий метод перевантаження, показаний раніше, коли в виклику IsObjectNullable використовується параметр типу Nullable <T>. Тому оптимальним рішенням є використання дво методичного підходу, проілюстрованого тут.
CAVEAT: Цей метод працює надійно, лише якщо його викликають з використанням оригінальної посилання на об'єкт або точної копії, як показано в прикладах. Однак якщо об’єкт, що зводить нанівець, розміщується в іншому типі (наприклад, об'єкт тощо) замість того, щоб залишатись у початковій формі Nullable <>, цей метод не працюватиме надійно. Якщо код, що викликає цей метод, не використовує оригінальну, немеханічну посилання на об'єкт або точну копію, він не може достовірно визначити зведеність об'єкта за допомогою цього методу.
У більшості сценаріїв кодування для визначення нульовості потрібно замість того, щоб перевірити тип оригінального об'єкта, а не його посилання (наприклад, код повинен мати доступ до оригінального типу об'єкта для визначення нульовості). У цих більш поширених випадках IsTypeNullable (див. Посилання) є надійним методом визначення нульовості.
PS - Про "зведеність"
Я повинен повторити заяву про мінливість, яку я зробив в окремому дописі, що стосується безпосередньо належного вирішення цієї теми. Тобто, я вважаю, що в центрі обговорення тут не повинно бути те, як перевірити, чи є об'єкт загальним типу Nullable, а скоріше, чи можна призначити значення null об’єкту такого типу. Іншими словами, я думаю, що ми повинні визначати, чи є тип об’єкта нульовим, а не чи є це Nullable. Різниця полягає в семантиці, а саме в практичних причинах визначення нульовості, що, як правило, має значення всього.
У системі, що використовує об'єкти з типами, можливо, невідомими до часу виконання (веб-сервіси, віддалені виклики, бази даних, канали тощо), загальною вимогою є визначення того, чи може бути призначений нуль об'єкту, чи може об'єкт містити нуль. Виконання таких операцій на ненульових типах, ймовірно, призведе до помилок, як правило, винятків, які є дуже дорогими як з точки зору продуктивності, так і з вимогами кодування. Щоб скористатися найбільш вподобаним підходом до попереднього уникнення подібних проблем, необхідно визначити, чи здатний об'єкт довільного типу містити нуль; тобто, чи це взагалі "зведений нанівець".
У дуже практичному та типовому сенсі, зведення в термін .NET зовсім не обов'язково означає, що Тип об'єкта є формою Nullable. У багатьох випадках фактично об'єкти мають еталонні типи, можуть містити нульове значення і, таким чином, всі є нульовими; жоден з них не має типу Nullable. Отже, для практичних цілей у більшості сценаріїв слід проводити тестування на загальну концепцію зведеності, порівняно з концепцією Nullable, залежною від реалізації. Таким чином, ми не повинні відмовлятися, зосереджуючись лише на типі .NET Nullable, а краще включати своє розуміння його вимог та поведінки в процесі зосередження на загальній практичній концепції зведення нанівець.