Если у С# есть ленивое ключевое слово
Если у С# есть ленивое ключевое слово, чтобы облегчить ленивую инициализацию?
например.
public lazy string LazyInitializeString = GetStringFromDatabase();
вместо
private string _backingField;
public string LazyInitializeString
{
get
{
if (_backingField == null)
_backingField = GetStringFromDatabase();
return _backingField;
}
}
Ответы
Ответ 1
Я не знаю о ключевом слове, но теперь он имеет тип System.Lazy<T>
.
- Официально это часть .Net Framework 4.0.
- Это позволяет ленивую загрузку значения для
member
.
- Он поддерживает
lambda expression
или method
для предоставления значения.
Пример:
public class ClassWithLazyMember
{
Lazy<String> lazySource;
public String LazyValue
{
get
{
if (lazySource == null)
{
lazySource = new Lazy<String>(GetStringFromDatabase);
// Same as lazySource = new Lazy<String>(() => "Hello, Lazy World!");
// or lazySource = new Lazy<String>(() => GetStringFromDatabase());
}
return lazySource.Value;
}
}
public String GetStringFromDatabase()
{
return "Hello, Lazy World!";
}
}
Тест:
var obj = new ClassWithLazyMember();
MessageBox.Show(obj.LazyValue); // Calls GetStringFromDatabase()
MessageBox.Show(obj.LazyValue); // Does not call GetStringFromDatabase()
В приведенном выше тестовом коде GetStringFromDatabase()
вызывается только один раз. Я думаю, это именно то, что вы хотите.
Изменить:
После комментариев от @dthorpe и @Joe, все, что я могу сказать, является самым коротким, что может быть:
public class ClassWithLazyMember
{
Lazy<String> lazySource;
public String LazyValue { get { return lazySource.Value; } }
public ClassWithLazyMember()
{
lazySource = new Lazy<String>(GetStringFromDatabase);
}
public String GetStringFromDatabase()
{
return "Hello, Lazy World!";
}
}
Потому что следующее не компилируется:
public Lazy<String> LazyInitializeString = new Lazy<String>(() =>
{
return GetStringFromDatabase();
});
И это свойство является типом Lazy<String>
not String
. Вам всегда нужно получить доступ к этому значению с помощью LazyInitializeString.Value
.
И я открыт для предложений о том, как сделать его короче.
Ответ 2
Рассматривали ли вы использование System.Lazy<T>
?
public Lazy<String> LazyInitializeString = new Lazy<String>(() =>
{
return GetStringFromDatabase();
});
(Это имеет тот недостаток, что вам нужно использовать LazyInitializeString.Value
вместо LazyInitializeString
.)
Ответ 3
Хорошо, вы говорите в комментарии, что Lazy<T>
вам не хватит, потому что он читается только, и вы должны называть его .Value
.
Тем не менее, ясно, что мы хотим что-то в этих строках - у нас уже есть синтаксис для описания действия, которое нужно вызывать, но не вызываемого немедленно (действительно, у нас есть три: лямбда, создание делегата и имя метода в виде ярлык для последнего - последнее, что нам нужно, это четвертый).
Но мы можем быстро собрать что-то, что делает это.
public enum SettableLazyThreadSafetyMode // a copy of LazyThreadSafetyMode - just use that if you only care for .NET4.0
{
None,
PublicationOnly,
ExecutionAndPublication
}
public class SettableLazy<T>
{
private T _value;
private volatile bool _isCreated;
private readonly Func<T> _factory;
private readonly object _lock;
private readonly SettableLazyThreadSafetyMode _mode;
public SettableLazy(T value, Func<T> factory, SettableLazyThreadSafetyMode mode)
{
if(null == factory)
throw new ArgumentNullException("factory");
if(!Enum.IsDefined(typeof(SettableLazyThreadSafetyMode), mode))
throw new ArgumentOutOfRangeException("mode");
_lock = (_mode = mode) == SettableLazyThreadSafetyMode.None ? null : new object();
_value = value;
_factory = factory;
_isCreated = true;
}
public SettableLazy(Func<T> factory, SettableLazyThreadSafetyMode mode)
:this(default(T), factory, mode)
{
_isCreated = false;
}
public SettableLazy(T value, SettableLazyThreadSafetyMode mode)
:this(value, () => Activator.CreateInstance<T>(), mode){}
public T Value
{
get
{
if(!_isCreated)
switch(_mode)
{
case SettableLazyThreadSafetyMode.None:
_value = _factory.Invoke();
_isCreated = true;
break;
case SettableLazyThreadSafetyMode.PublicationOnly:
T value = _factory.Invoke();
if(!_isCreated)
lock(_lock)
if(!_isCreated)
{
_value = value;
Thread.MemoryBarrier(); // ensure all writes involved in setting _value are flushed.
_isCreated = true;
}
break;
case SettableLazyThreadSafetyMode.ExecutionAndPublication:
lock(_lock)
{
if(!_isCreated)
{
_value = _factory.Invoke();
Thread.MemoryBarrier();
_isCreated = true;
}
}
break;
}
return _value;
}
set
{
if(_mode == SettableLazyThreadSafetyMode.None)
{
_value = value;
_isCreated = true;
}
else
lock(_lock)
{
_value = value;
Thread.MemoryBarrier();
_isCreated = true;
}
}
}
public void Reset()
{
if(_mode == SettableLazyThreadSafetyMode.None)
{
_value = default(T); // not strictly needed, but has impact if T is, or contains, large reference type and we really want GC to collect.
_isCreated = false;
}
else
lock(_lock) //likewise, we could skip all this and just do _isCreated = false, but memory pressure could be high in some cases
{
_value = default(T);
Thread.MemoryBarrier();
_isCreated = false;
}
}
public override string ToString()
{
return Value.ToString();
}
public static implicit operator T(SettableLazy<T> lazy)
{
return lazy.Value;
}
public static implicit operator SettableLazy<T>(T value)
{
return new SettableLazy<T>(value, SettableLazyThreadSafetyMode.ExecutionAndPublication);
}
}
Добавление некоторых дополнительных перегрузок конструктора остается как упражнение для читателя:)
Этого будет более чем достаточно:
private SettableLazy<string> _backingLazy = new SettableLazy<string>(GetStringFromDatabase);
public string LazyInitializeString
{
get
{
return _backingLazy;
}
set
{
_backingLazy = value;
}
}
Лично я не радуюсь неявным операторам, но они показывают, что ваши требования могут быть выполнены. Конечно, нет необходимости в другой языковой функции.