Предположим, я создал класс-оболочку, подобный следующему:
public class Foo : IFoo
{
private readonly IFoo innerFoo;
public Foo(IFoo innerFoo)
{
this.innerFoo = innerFoo;
}
public int? Bar { get; set; }
public int? Baz { get; set; }
}
Идея заключается в том, что innerFoo
может обернуть методы доступа к данным или что-то аналогично дорогое, и я хочу, чтобы его GetBar
и GetBaz
методы вызывались только один раз. Поэтому я хочу создать еще одну оболочку, которая будет сохранять значения, полученные при первом запуске.
Это достаточно просто сделать, конечно:
int IFoo.GetBar()
{
if ((Bar == null) && (innerFoo != null))
Bar = innerFoo.GetBar();
return Bar ?? 0;
}
int IFoo.GetBaz()
{
if ((Baz == null) && (innerFoo != null))
Baz = innerFoo.GetBaz();
return Baz ?? 0;
}
Но это становится довольно повторяющимся, если я делаю это с 10 различными свойствами и 30 различными обертками. Итак, я подумала, эй, давайте сделаем это общее:
T LazyLoad<T>(ref T prop, Func<IFoo, T> loader)
{
if ((prop == null) && (innerFoo != null))
prop = loader(innerFoo);
return prop;
}
Который почти доставит меня туда, куда я хочу, но не совсем, потому что вы не можете ref
авто-свойство (или любое свойство вообще). Другими словами, я не могу написать это:
int IFoo.GetBar()
{
return LazyLoad(ref Bar, f => f.GetBar()); // <--- Won't compile
}
Вместо этого мне нужно изменить Bar
, чтобы иметь явное поле поддержки и писать явные методы получения и установки. Это нормально, за исключением того факта, что я пишу еще более избыточный код, чем прежде.
Затем я рассмотрел возможность использования деревьев выражений:
T LazyLoad<T>(Expression<Func<T>> propExpr, Func<IFoo, T> loader)
{
var memberExpression = propExpr.Body as MemberExpression;
if (memberExpression != null)
{
// Use Reflection to inspect/set the property
}
}
Это хорошо работает с рефакторингом - это будет прекрасно работать, если я сделаю это:
return LazyLoad(f => f.Bar, f => f.GetBar());
Но на самом деле это не безопасно , потому что кто-то менее умный (т.е. я сам через 3 дня, когда я неизбежно забуду, как это реализовано внутри) может решить написать вместо этого:
return LazyLoad(f => 3, f => f.GetBar());
Что может привести к сбою или к неожиданному / неопределенному поведению, в зависимости от того, насколько я защищенно пишу метод LazyLoad
. Поэтому мне не очень нравится этот подход, потому что он приводит к возможности ошибок во время выполнения, которые были бы предотвращены с первой попытки. Он также опирается на Reflection, которая здесь выглядит немного грязной, хотя этот код, по общему признанию, не чувствителен к производительности.
Теперь я мог бы также принять решение сделать все возможное и использовать DynamicProxy для перехвата методов и не должен писать любой код, а на самом деле я уже сделать это в некоторых приложениях. Но этот код находится в базовой библиотеке, от которой зависит множество других сборок, и, кажется, ужасно неправильно представлять такой вид сложности на таком низком уровне. Отделение реализации, основанной на перехватчиках, от интерфейса IFoo
путем помещения его в собственную сборку не очень помогает; Дело в том, что этот самый класс все еще будет использоваться повсеместно, должен использоваться, так что это не одна из тех проблем, которые можно было бы тривиально решить с помощью небольшого количества DI-магии.
Последний вариант, о котором я уже подумал, будет иметь такой метод:
T LazyLoad<T>(Func<T> getter, Action<T> setter, Func<IFoo, T> loader) { ... }
Эта опция также очень "meh" - она избегает Reflection, но все еще подвержена ошибкам, и , она на самом деле не так сильно уменьшает повторение. Это почти так же плохо, как писать явные методы получения и установки для каждого свойства.
Может быть, я просто невероятно придирчив, но это приложение все еще находится на ранних стадиях, и со временем оно существенно вырастет, и я действительно хочу, чтобы код был чистым и чистым.
Итог: я в тупике, ищу другие идеи.
Вопрос:
Есть ли способ очистить код с отложенной загрузкой вверху, так что реализация будет:
- Гарантия безопасности во время компиляции, такая как
ref
версия;
- На самом деле уменьшить количество повторений кода, как в
Expression
версии; и
- Не принимать какие-либо существенные дополнительные зависимости?
Другими словами, есть ли способ сделать это, просто используя обычные функции языка C # и, возможно, несколько небольших вспомогательных классов? Или я просто должен согласиться с тем, что здесь есть компромисс, и исключить одно из вышеуказанных требований из списка?