Почему это использование неявных бросков не работает?
Я определил общий класс "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;