Почему это использование неявных бросков не работает?

Я определил общий класс "Lazy<T>", для ленивой оценки и кэширования результата делегата Func<T>.

Я также определяю два неявных оператора трансляции, поэтому я могу создать Lazy<T> из Func<T> s, и я могу назначить Lazy<T> a T (получает Value Lazy<T>)

Идея состоит в том, что вы можете передать Lazy<T> вместо экземпляра T, но не выполнять работу по вычислению/извлечения значения до тех пор, пока он не будет назначен фактическому экземпляру T.

// class Lazy<T>
// Encapsulates a value which can be retrieved when first accessed, 
// and is then cached.
class Lazy<T>
{
  private Func<T> _getter;
  private T _cached;
  private bool _isCached;

  // Get/set the getter delegate
  // that 'calculates' the value.
  public Func<T> Getter
  {
    get
    {
      return _getter;
    }
    set 
    {
      _getter = value;
      _cached = default(T);
      _isCached = false;
    }
  }

  // Get/set the value.
  public T Value
  {
    get 
    {
      if (!_isCached) 
      {
        _cached = Getter();
        _isCached = true;
        _getter = null;
      }
      return _cached;
    }
    set
    {
      _cached = value;
      _isCached = true;
      _getter = null;
    }
  }

  // Implicit casts:

  // Create a T from a Lazy<T>
  public static implicit operator T(Lazy<T> lazy) 
  {
    return lazy.Value;
  }

  // Create a Lazy<T> from a Func<T>
  public static implicit operator Lazy<T>(Func<T> getter)
  {
    return new Lazy<T> {Getter = getter};
  }
}

Но этот класс не работает, как я ожидал, в одном случае, который показан в тестовом приложении ниже:

class Program
{
  static void Main()
  {
    // This works okay (1)
    TestLazy(() => MakeStringList());

    // This also works (2)
    Lazy<string> lazyString = new Func<string>(() => "xyz");
    string s = lazyString;

    //This doesn't compile (3)
    //
    Lazy<IList<string>> lazyStrings = new Func<IList<string>>(MakeStringList);
    IList<string> strings = lazyStrings; //ERROR
  }


  static void TestLazy<T>(Func<T> getter)
  {
    Lazy<T> lazy = getter;
    T nonLazy = lazy;
  }

  private static IList<string> MakeStringList()
  {
    return new List<string> { new string('-', 10) };
  }
}

В строке, помеченной //ERROR, я получаю ошибку компиляции:

ошибка CS0266: Невозможно неявно преобразовать тип Lazy<System.Collections.Generic.IList<string>> в System.Collections.Generic.IList<string>. Явное преобразование существует (вы пропускаете листинг?)

Эта ошибка сбивает с толку, поскольку существует неявный перевод из источника в заданный тип. И, на первый взгляд, блок кода (3) делает то же самое, что (1) Кроме того, он отличается от (2) только типом, используемым для специализации Lazy.

Может кто-нибудь объяснить мне, что здесь происходит?

Ответ 1

Проблема заключается в том, что вы пытаетесь конвертировать в IList<T> неявно, а IList<T> не включает в себя IList<T> (даже если они одного типа) - только преобразования типов без интерфейса рассматриваемых в охвате. Из раздела 6.4.3 спецификации С# 3.0:

Если стандартное неявное преобразование (§6.3.1) существует от типа А до тип B, , и если ни A, ни B не являются интерфейсные типы, то A называется охватывает B, а B - Включить A.

В разделе 6.4.4, говоря о пользовательских конверсиях, один из шагов (внимание мой):

  • Найти набор применимых пользовательских и отмененных преобразований операторов, U.

Этот набор состоит из определяемого пользователем и сняли неявное преобразование операторов, объявленных классами или структур в D, которые преобразуются из типа охватывая S типу, охватываемому по T. Если U пусто, преобразование undefined и ошибка времени компиляции имеет место.

IList<T> не охватывает IList<T>, поэтому этот шаг не выполняется.

Компилятор будет делать "прикованные" неявные преобразования в других сценариях, хотя - так, если у вас действительно есть Lazy<List<T>>, вы могли бы написать:

object strings = lazyStrings;

работает, потому что List<T> охватывает object (поскольку оба являются классами, а там стандартное неявное преобразование от List<T> до object).

Теперь, почему это так, я подозреваю, что он останавливает нечетные случаи, когда вы ожидаете конверсии ссылок, но вы действительно получите неявное преобразование. Предположим, что мы имели:

class ListLazy : Lazy<IList<string>>, IList<string>
{
    // Stuff
}
...
Lazy<IList<string>> x = new ListLazy();
IList<string> list = x;

Какое преобразование должно использоваться? Там неявное преобразование ссылок из фактического типа в IList<string>... но компилятор этого не знает, потому что выражение имеет тип Lazy<IList<string>>. В основном интерфейсы неудобны, потому что они могут появиться позже в иерархии типов, тогда как с классом вы всегда знаете, где вы находитесь, если вы понимаете, что я имею в виду. (Неявные преобразования, которые включают два класса в одной иерархии, запрещены.)

Ответ 2

Может ли это быть опечатка?

Lazy<List<string>> lazyStrings = new Func<List<string>>(MakeStringList);
IList<string> strings = lazyStrings; //ERROR
List<string> strings = lazyStrings; //OK

Если вам нужен IList < > , это двухэтапное преобразование, и я полагаю, что компилятор не хочет опережать себя

IList<string> istrings = strings;