Есть ли способ использовать метод расширения в блоке инициализатора объекта в С#?

StackOverflow https://stackoverflow.com/questions/132245

Вопрос

Простая демонстрация ниже отражает то, что я пытаюсь сделать.В реальной программе мне приходится использовать блок инициализатора объекта, поскольку он читает список в выражении выбора 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");
    }

И это прекрасно работает.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top