порожнеча в C # generics?


94

У мене є загальний метод, який приймає запит і надає відповідь.

public Tres DoSomething<Tres, Treq>(Tres response, Treq request)
{/*stuff*/}

Але мені не завжди потрібна відповідь на мій запит, і я не завжди хочу подавати дані запиту, щоб отримати відповідь. Я також не хочу копіювати та вставляти методи в цілому, щоб вносити незначні зміни. Що я хочу, це мати можливість зробити це:

public Tre DoSomething<Tres>(Tres response)
{
    return DoSomething<Tres, void>(response, null);
}

Це можливо якимось чином? Здається, що конкретно використання void не працює, але я сподіваюся знайти щось аналогічне.


1
Чому б просто не використовувати System.Object і не виконати нульову перевірку в DoSomething (відповідь Tres, запит Treq)?
Джеймс

Зверніть увагу, що вам потрібно використовувати повернене значення. Ви можете викликати такі функції, як процедури. DoSomething(x);замістьy = DoSomething(x);
Олів'є Якот-Декомб

1
Я думаю, ви хотіли сказати: "Зверніть увагу, що вам не потрібно використовувати повернене значення". @ OlivierJacot-Descombes
zanedp

Відповіді:


95

Ви не можете використовувати void, але можете використовувати object: це невелика незручність, тому що ваші потенційні voidфункції повинні повернутися null, але якщо він уніфікує ваш код, це має бути невелика ціна.

Ця неможливість використовувати voidяк тип повернення принаймні частково відповідає за розподіл між Func<...>та Action<...>сім'ями загальних делегатів: якби можна було повернутися void, все Action<X,Y,Z>стало б просто Func<X,Y,Z,void>. На жаль, це неможливо.


45
(Жарт) І він все ще може повернутися voidвід тих можливих методів, с return System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(void));. Однак це буде порожня порожнеча.
Jeppe Stig Nielsen

Оскільки C # підтримує більш функціональні функції програмування, ви можете поглянути на Unit, який представляє voidFP. І для цього є вагомі причини. У F #, все ще .NET, ми unitвбудовані.
Джо

88

Ні, на жаль, ні. Якби це voidбув "реальний" тип (як, наприклад, unitу F # ), життя було б набагато простішим у багатьох відношеннях. Зокрема, нам не будуть потрібні як сім'ї, так Func<T>і Action<T>сім'ї - їх просто буде Func<void>замість Action, Func<T, void>замість Action<T>тощо.

Це також спростило б асинхронізацію - взагалі не було б потреби в не-загальному Taskтипі - ми б просто мали Task<void>.

На жаль, це не так, як працюють системи типу C # або .NET ...


4
Unfortunately, that's not the way the C# or .NET type systems work...Ви змушували мене сподіватися, що, можливо, з часом все може так заробити. Чи означає ваша остання думка, що у нас навряд чи колись все буде працювати так?
Dave Cousineau

2
@Sahuagin: Я підозрюю, що ні - на даний момент це була б досить велика зміна.
Джон Скіт,

1
@stannius: Ні, я думаю, це більше незручності, ніж щось, що призводить до неправильного коду.
Джон Скіт,

2
Чи є перевага мати тип посилання на одиницю перед порожнім типом значення, щоб представляти "порожнечу"? Тип порожнього значення мені здається більше підходить, він не несе значення і не займає місця. Цікаво, чому void не був реалізований таким чином. Поппінг або не вискакування його зі стеку не матиме ніякої різниці (розмовляючи про рідний код, в IL це може бути інакше).
Ondrej Petrzilka 11.03.18

1
@Ondrej: Я вже намагався використовувати порожню структуру для речей, і вона в кінцевому підсумку не є порожньою в CLR ... Звичайно, це може бути спеціальним випадком. Крім цього, я не знаю, що б я запропонував; Я над цим не надто думав.
Джон Скіт,

27

Ось що ви можете зробити. Як сказав @JohnSkeet, у C # немає типу одиниці, то зробіть це самостійно!

public sealed class ThankYou {
   private ThankYou() { }
   private readonly static ThankYou bye = new ThankYou();
   public static ThankYou Bye { get { return bye; } }
}

Тепер ви завжди можете використовувати Func<..., ThankYou>замістьAction<...>

public ThankYou MethodWithNoResult() {
   /* do things */
   return ThankYou.Bye;
}

Або скористайтеся чимось, що вже зроблено командою Rx: http://msdn.microsoft.com/en-us/library/system.reactive.unit%28v=VS.103%29.aspx


System.Reactive.Unit - хороша порада. Станом на листопад 2016 року, якщо ви зацікавлені отримати якомога меншу частину фреймворка Reactive, оскільки ви не використовуєте нічого, крім класу Unit, використовуйте менеджер пакунків Install-Package System.Reactive.Core
nuget

6
Перейменуйте "Дякую" на "KThx", і він переможе. ^ _ ^Kthx.Bye;
LexH

Тільки для перевірки, що я чогось не пропускаю .. До побачення геттер не додає нічого значущого за прямий доступ тут?
Ендрю

1
@Andrew, вам не потрібен до побачення, якщо ви не хочете слідувати духу кодування C #, який говорить, що ви не повинні виставляти голі поля
Trident D'Gao

16

Ви можете просто використовувати, Objectяк пропонували інші. Або Int32що я бачив користь. Використання Int32вводить "фіктивне" число (використання 0), але принаймні ви не можете помістити будь-який великий та екзотичний об'єкт у Int32посилання (структури запечатані).

Ви також можете написати власний тип "void":

public sealed class MyVoid
{
  MyVoid()
  {
    throw new InvalidOperationException("Don't instantiate MyVoid.");
  }
}

MyVoidпосилання дозволені (це не статичний клас), але можуть бути лише null. Конструктор екземпляра є приватним (і якщо хтось намагається викликати цей приватний конструктор за допомогою відображення, на нього буде висунуто виняток).


Оскільки були введені кортежі значень (2017, .NET 4.7), можливо, природно використовувати структуруValueTuple (0-кортеж, не-загальний варіант) замість такого MyVoid. Його екземпляр має значення, ToString()яке повертається "()", тому воно виглядає як нуль-кортеж. Станом на поточну версію C #, ви не можете використовувати маркери ()в коді для отримання екземпляра. Замість цього ви можете використовувати default(ValueTuple)або просто default(коли тип можна вивести з контексту).


2
Іншою назвою для цього буде нульовий шаблон об'єкта (шаблон дизайну).
Aelphaeis

@Aelphaeis Ця концепція трохи відрізняється від шаблону нульових об'єктів. Тут справа лише в тому, щоб мати якийсь тип, який ми можемо використовувати із загальним. Мета з нульовим шаблоном об'єкта - уникнути написання методу, який повертає null, щоб вказати особливий випадок, а замість цього повернути фактичний об'єкт із відповідною поведінкою за замовчуванням.
Ендрю Палмер,

8

Мені подобається ідея Олексія Бикова вище, але її можна було б дещо спростити

public sealed class Nothing {
    public static Nothing AtAll { get { return null; } }
}

Оскільки я не бачу жодної видимої причини, чому Nothing.AtAll не міг просто дати null

Та сама ідея (або ідея Джеппе Стіг Нільсен) також чудово підходить для використання з набраними класами.

Наприклад, якщо тип використовується лише для опису аргументів процедури / функції, переданої як аргумент якомусь методу, і сам він не бере жодних аргументів.

(Вам все одно потрібно буде зробити фіктивну обгортку або дозволити необов'язкове "Нічого". Але IMHO використання класу виглядає добре з myClass <Нічого>)

void myProcWithNoArguments(Nothing Dummy){
     myProcWithNoArguments(){
}

або

void myProcWithNoArguments(Nothing Dummy=null){
    ...
}

1
Значення nullмає відтінок відсутності або відсутності object. Маючи лише одне значення для Nothingзасобу, воно ніколи не виглядає як щось інше.
LexH

Це гарна ідея, але я погоджуюсь з @LexieHankins. Я думаю, було б краще, щоб "взагалі нічого" було унікальним екземпляром класу, що Nothingзберігається у приватному статичному полі та додає приватний конструктор. Той факт, що нуль все ще можливий, дратує, але це буде вирішено, сподіваємося, з C # 8.
Kirk Woll

З академічної точки зору я розумію цю відмінність, але це буде дуже особливий випадок, коли ця відмінність має значення. (Уявіть собі функцію загального типового типу, де повернення "null" використовується як спеціальний маркер, наприклад, як якийсь маркер помилки)
Еске Ран,

4

voidхоча і є типом, є дійсним лише як тип повернення методу.

Неможливо обійти це обмеження void.


1

Що я зараз роблю, це створювати власні закриті типи за допомогою приватного конструктора. Це краще, ніж кидати винятки в c-tor, тому що вам не потрібно діставатись до часу виконання, щоб зрозуміти, що ситуація неправильна. Це дещо краще, ніж повернення статичного екземпляра, тому що вам не доведеться розподіляти навіть один раз. Це дещо краще, ніж повернення статичного нуля, оскільки воно менш детальне на стороні виклику. Єдине, що може зробити абонент, це нуль.

public sealed class Void {
    private Void() { }
}

public sealed class None {
    private None() { }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.