void в дженериках C #?

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);
Olivier Jacot-Descombes
1
Я думаю, вы хотели сказать: «Обратите внимание, что вам не нужно использовать возвращаемое значение». @ OlivierJacot-Descombes
zanedp

Ответы:

98

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

Эта неспособность использовать voidв качестве возвращаемого типа, по крайней мере, частично ответственна за разделение между Func<...>и Action<...>семействами универсальных делегатов: если бы можно было вернуть void, все Action<X,Y,Z>стало бы просто Func<X,Y,Z,void>. К сожалению, это невозможно.

Сергей Калиниченко
источник
48
(Шутка) И он все еще может вернуться voidиз этих бессмысленных методов с помощью return System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(void));. Но это будет пустота в коробке.
Jeppe Stig Nielsen
1
Поскольку C # поддерживает больше функций функционального программирования, вы можете взглянуть на модуль , представленный voidв FP. И есть веские причины его использовать. В 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: Я подозреваю, что нет - на данный момент это было бы довольно большим изменением.
Jon Skeet
1
@stannius: Нет, я думаю, это скорее неудобство, чем то, что приводит к неправильному коду.
Джон Скит
2
Есть ли преимущество иметь ссылочный тип модуля над пустым типом значения для представления «void»? Мне больше подходит пустой тип значения, он не несет значения и не занимает места. Интересно, почему void так не реализовали. Выталкивать или не извлекать его из стека не имеет значения (если говорить о собственном коде, в IL это может быть иначе).
Ондрей Петрзилка
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 года, если вы заинтересованы в получении как можно Install-Package System.Reactive.Core
меньшего
6
Переименуйте ThankYou в «KThx», и он станет победителем. ^ _ ^Kthx.Bye;
LexH
Просто чтобы проверить, что я чего-то не упускаю ... Bye getter не добавляет ничего значительного по сравнению с прямым доступом, вот так?
Эндрю
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

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

Например, если тип используется только для описания аргументов процедуры / функции, переданной в качестве аргумента некоторому методу, и сам по себе не принимает никаких аргументов.

(Вам все равно нужно будет создать фиктивную оболочку или разрешить необязательное «Nothing». Но, IMHO, использование класса выглядит хорошо с myClass <Nothing>)

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

или

void myProcWithNoArguments(Nothing Dummy=null){
    ...
}
Эске Ран
источник
1
Значение nullимеет оттенок отсутствия или отсутствия object. Одно значение Nothingозначает, что оно никогда не будет похоже ни на что другое.
LexH
Это хорошая идея, но я согласен с @LexieHankins. Я думаю, что лучше было бы, чтобы «вообще ничего» было уникальным экземпляром класса, Nothingхранящимся в частном статическом поле и добавляющим частный конструктор. Тот факт, что null все еще возможен, раздражает, но это будет решено, надеюсь, с помощью C # 8.
Кирк Уолл
С академической точки зрения я понимаю различие, но это будет особый случай, когда различие имеет значение. (Представьте себе функцию универсального типа, допускающего значение NULL, где возврат «null» используется в качестве специального маркера, например, как своего рода маркер ошибки)
Эске Ран
4

voidхотя и является типом, он действителен только как возвращаемый тип метода.

Это ограничение невозможно обойти void.

Одед
источник
1

В настоящее время я создаю собственные запечатанные типы с помощью частного конструктора. Это лучше, чем бросать исключения в c-tor, потому что вам не нужно дойти до времени выполнения, чтобы понять, что ситуация неверна. Это немного лучше, чем возврат статического экземпляра, потому что вам не нужно выделять ни разу. Это немного лучше, чем возврат статического нуля, потому что он менее подробен на стороне вызова. Единственное, что может сделать вызывающий - дать null.

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

public sealed class None {
    private None() { }
}
ГРУ
источник