Есть ли способ использовать метод расширения в блоке инициализатора объекта в С#?
-
02-07-2019 - |
Вопрос
Простая демонстрация ниже отражает то, что я пытаюсь сделать.В реальной программе мне приходится использовать блок инициализатора объекта, поскольку он читает список в выражении выбора LINQ to SQL, и есть значение, которое я хочу прочитать из базы данных и сохранить в объекте, но объект не имеет простого свойства, которое я мог бы установить для этого значения.Вместо этого у него есть хранилище данных XML.
Похоже, я не могу вызвать метод расширения в блоке инициализатора объекта и не могу прикрепить свойство с помощью методов расширения.
Так мне не повезло с этим подходом?Единственная альтернатива — убедить владельца базового класса изменить его для этого сценария.
У меня есть существующее решение, в котором я создаю подкласс BaseDataObject, но здесь тоже есть проблемы, которые не проявляются в этом простом примере.Объекты сохраняются и восстанавливаются как BaseDataObject — приведения типов и тесты могут усложниться.
public class BaseDataObject
{
// internal data store
private Dictionary<string, object> attachedData = new Dictionary<string, object>();
public void SetData(string key, object value)
{
attachedData[key] = value;
}
public object GetData(string key)
{
return attachedData[key];
}
public int SomeValue { get; set; }
public int SomeOtherValue { get; set; }
}
public static class Extensions
{
public static void SetBarValue(this BaseDataObject dataObject,
int barValue)
{
/// Cannot attach a property to BaseDataObject?
dataObject.SetData("bar", barValue);
}
}
public class TestDemo
{
public void CreateTest()
{
// this works
BaseDataObject test1 = new BaseDataObject
{ SomeValue = 3, SomeOtherValue = 4 };
// this does not work - it does not compile
// cannot use extension method in the initialiser block
// cannot make an exension property
BaseDataObject test2 = new BaseDataObject { SomeValue = 3, SomeOtherValue = 4, SetBarValue(5) };
}
}
Один из ответов (от mattlant) предлагает использовать метод расширения свободного стиля интерфейса.например.:
// fluent interface style
public static BaseDataObject SetBarValueWithReturn(this BaseDataObject dataObject, int barValue)
{
dataObject.SetData("bar", barValue);
return dataObject;
}
// this works
BaseDataObject test3 = (new BaseDataObject { SomeValue = 3, SomeOtherValue = 4 }).SetBarValueWithReturn(5);
Но будет ли это работать в запросе LINQ?
Решение
Даже лучше:
public static T SetBarValue<T>(this T dataObject, int barValue)
where T : BaseDataObject
{
dataObject.SetData("bar", barValue);
return dataObject;
}
и вы можете использовать этот метод расширения для производных типов BaseDataObject для объединения методов без приведения и сохранения реального типа при его выводе в поле var или анонимный тип.
Другие советы
Инициализаторы объектов — это просто синтаксический сахар, требующий умного компилятора, и в текущей реализации вы не можете вызывать методы в инициализаторе.
var x = new BaseDataObject { SomeValue = 3, SomeOtherValue = 4 };
Получит компилятор что-то вроде этого:
BaseDataObject tempObject = new BaseDataObject();
tempObject.SomeValue = 3;
tempObject.SomeOtherValue = 4;
BaseDataObject x = tempObject;
Разница в том, что проблем с синхронизацией быть не может.Переменной x get сразу присваивается полностью присвоенный объект BaseDataObject, вы не можете возиться с объектом во время его инициализации.
Вы можете просто вызвать метод расширения после создания объекта:
var x = new BaseDataObject { SomeValue = 3, SomeOtherValue = 4 };
x.SetBarValue()
Вы можете изменить SetBarValue на свойство с помощью get/set, которое можно назначить во время инициализации:
public int BarValue
{
set
{
//Value should be ignored
}
}
Или вы можете подклассифицировать/использовать шаблон фасада, чтобы добавить метод в свой объект:
public class DataObjectWithBarValue : BaseDataObject
{
public void BarValue
{
set
{
SetData("bar", value);
}
get
{
(int) GetData("bar");
}
}
}
Нет, но вы могли бы сделать это....:
BaseDataObject test2 = (new BaseDataObject { SomeValue = 3, SomeOtherValue = 4}).SetBarValue(5);
И пусть ваше расширение возвращает объект, как Linq Does.
РЕДАКТИРОВАТЬ:Это была хорошая мысль, пока я не перечитал и не увидел, что базовый класс был разработан третьим человеком:то есть у тебя нет кода.Другие здесь опубликовали правильное решение.
static T WithBarValue<T>(this T dataObject, int barValue)
where T : BaseDataObject
{ dataObject.SetData("bar", barValue);
return dataObject;
}
var x = new BaseDataObject{SomeValue=3, OtherValue=4}.WithBarValue(5);
Возможно ли расширение класса?Тогда вы сможете легко добавить необходимое вам свойство.
В противном случае вы можете создать новый класс с похожими свойствами, который просто обращается к частному экземпляру интересующего вас класса.
Правильно, узнав от ответов, краткий ответ «Есть ли способ использовать метод расширения в блоке инициализатора объекта в C#?» является "Нет."
Способ, с помощью которого я в конечном итоге решил проблему, с которой столкнулся (похожую, но более сложную, чем игрушечная проблема, которую я здесь поставил), был гибридным подходом, а именно:
Я создал подкласс, например.
public class SubClassedDataObject : BaseDataObject
{
public int Bar
{
get { return (int)GetData("bar"); }
set { SetData("bar", value); }
}
}
Это отлично работает в LINQ, блок инициализации выглядит так
SubClassedDataObject testSub = new SubClassedDataObject
{ SomeValue = 3, SomeOtherValue = 4, Bar = 5 };
Но причина, по которой мне в первую очередь не понравился этот подход, заключается в том, что эти объекты помещаются в XML и возвращаются как BaseDataObject, а обратное приведение будет раздражать, создавать ненужную копию данных и создавать две копии. одного и того же объекта в игре.
В остальной части кода я проигнорировал подклассы и использовал методы расширения:
public static void SetBar(this BaseDataObject dataObject, int barValue)
{
dataObject.SetData("bar", barValue);
}
public static int GetBar(this BaseDataObject dataObject)
{
return (int)dataObject.GetData("bar");
}
И это прекрасно работает.