Любой способ вернуть объект, который еще не существует, но будет позже из метода C #? - PullRequest
1 голос
/ 28 августа 2010

Отказ от ответственности 1: Сумасшедший педантичный изгиб языка впереди.

Отказ от ответственности 2: Любым клиентам, присутствующим или будущим - я не выставляю вам счет за это.

Хорошо, поэтому на самом деле это не необходимо , но я бездельничаюс созданием плагинов для xunit.net и появляется интересный сценарий

В настоящее время пример расширения SubSpec, поставляемого с исходным кодом, работает примерно так:

[Specification]
void calculator_addition() {
  Calculator calc = null;
  User user = null;
  "Given a calculator and a user".Context(()=> {
       calc = new Calculator();
       user = new User(calculationQuota: 100);
   });
  "adding 1 + 1".Do(()=>result = calc.Add(1, 1));
  "will equal 2".Assert(()=>calc.Result.ShouldEqual(2));
  "will decrease user's quota by one".Assert(()=>user.CalculationsLeft.ShouldEqual(99));
}

Это не красивоэлегантный C #, который я знаю и люблю - мне не нравится объявлять неинициализированные переменные, и я предпочитаю не объявлять их вообще.Я бы предпочел сделать что-то вроде этого:

[Specification]
void calculator_addition() {
  var _ =
  "Given a calculator and a user".Context(()=> new {
       Calc = new Calculator(),
       User = new User(calculationQuota: 100),
   });
  "adding 1 + 1".Do(()=>_.Calc.Add(1, 1));
  "will equal 2".Assert(()=>_.Calc.Result.ShouldEqual(2));
  "will decrease user's quota by one".Assert(()=>_.User.CalculationsLeft.ShouldEqual(99));
}

В этом случае метод расширения Context () будет иметь подпись void Context(this string, Action setUpWith) и подпись T Context<T>(this string, Func<T> setUpWith).Интересная проблема возникает в реализации, хотя, поскольку делегат setUpWith фактически не выполняется в настоящее время, он просто сохраняется, а затем выполняется пользовательским атрибутом Спецификации.Это означает, что на самом деле нет Т для возврата.Тем не менее, поскольку сохраненные делегаты выполняются по порядку, я могу гарантировать, что он будет существовать к моменту вызова делегата в методе Do ().

Так что мне хотелось бы вернуть динамический проксидля T, но это на самом деле неосуществимо, поскольку я хотел бы иметь возможность использовать анонимные типы, которые сделали бы T запечатанным.

Теперь, на земле C ++, я считаю, что можно было бы выделить память для объекта и вернуть «объект» в качестве ссылки на этот бит пространства, который затем будет заполнен в конце концов.По сути, это то, что произошло бы, если бы я передал T в качестве параметра ref (но тогда я должен был бы объявить его, отвергая точку).

Тем не менее, есть углы C #, которые я не исследовал.Кто-нибудь может придумать способ выжать из этого желаемый синтаксис?

PS.У меня есть пара решений с небольшими изменениями в синтаксисе, которые я опубликую как ответы ниже.

Ответы [ 2 ]

4 голосов
/ 28 августа 2010

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

Я не очень хорошо знаком с API, о котором вы говорите, но если вы изменили API так, чтобы он выглядел примерно так, то я думаю, что он должен работать без мутаций:

"Given a calculator and a user"
   // Constructs and returns anonymous object that will hold the state
   // ('Context' takes 'Func<T>' and returns some wrapper)
   .Context(() => new { Calc = new Calculator(); 
                        User = new User(calculationQuota: 100) })
   .Then(ctx => {
      "adding 1 + 1".Do(() => result = ctx.Calc.Add(1, 1)); 
      "will equal 2".Assert(() => ctx.Calc.Result.ShouldEqual(2)); 
      "will decrease user's quota by one".Assert(() => 
         ctx.User.CalculationsLeft.ShouldEqual(99)); 
   });

Обернув тело, которое должно выполняться после инициализации контекста, в другое лямбда-выражение, вы сможете избежать проблемы с инициализацией. Метод Then просто сохранит где-нибудь лямбда-функцию (и вызовет ее с возвращенным контекстом после инициализации состояния).

0 голосов
/ 28 августа 2010

Хорошо, вот мои возможные решения, которые приближают меня к синтаксису, который я хочу

  • Я изменяю сигнатуру метода следующим образом:

    dynamic Context(this string msg, Func<dynamic> setUpWith);

Здесь я бы вернул реализацию DynamicObject, которая проходит через любые запросы свойств или методов к тому, что возвращается при выполнении setUpWith.

Плюсы: Довольно близко к тому, что я хочу

Минусы: C # 4.0 только.Реализация DynamicObject является проблемой.Нет поддержки intellisense.

  • Я изменяю сигнатуру метода следующим образом:

    Lazy<T> Context<T>(this string msg, Func<T> setUpWith);

Плюсы: Intellisense.Простота реализации.

Минусы: Приходится вызывать. Значение перед доступом к базовому типу, который раздражает.

  • Я изменяю сигнатуры других методов наследующее:

    void Do(this string msg, Func<dynamic> doThis);

, которые вызываются с помощью

  "adding 1 + 1".Do(x=>x.Calc.Add(1, 1));
  "will decrease user's quota by one".Assert(x=>x.User.CalculationsLeft.ShouldEqual(99));

Где значение хранится и вводится в рамках спецификации.

Плюсы: Нет необходимости отслеживать переменные вне области замыкания вообще.

Минусы: Нет смысла.Нужно поменять два метода вместо одного, так что больше кода для рефакторинга.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...