Есть ли способ использовать метод расширения в блоке инициализатора объекта в C # - PullRequest
7 голосов
/ 25 сентября 2008

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

// 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?

Ответы [ 6 ]

5 голосов
/ 25 сентября 2008

Инициализаторы объектов - это просто синтаксический сахар, который требует умного компилятора, и с текущей реализации вы не можете вызывать методы в инициализаторе.

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");
        }
    }
}
4 голосов
/ 25 сентября 2008

Нет, но вы могли бы сделать это ....:

BaseDataObject test2 = (new BaseDataObject { SomeValue = 3, SomeOtherValue = 4}).SetBarValue(5);

И ваше расширение должно возвращать объект как Linq Does.

РЕДАКТИРОВАТЬ: Это была хорошая мысль, пока я перечитал и увидел, что базовый класс был разработан третьим лицом: иначе у вас нет кода. Другие здесь выложили правильное решение.

3 голосов
/ 25 сентября 2008

Еще лучше:

public static T SetBarValue<T>(this T dataObject, int barValue)
        where T : BaseDataObject 
    {
        dataObject.SetData("bar", barValue);
        return dataObject;
    }

и вы можете использовать этот метод расширения для производных типов BaseDataObject, чтобы связывать методы без приведений и сохранять реальный тип при выводе в поле var или анонимный тип.

1 голос
/ 25 сентября 2008
 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);
0 голосов
/ 26 сентября 2008

Правильно, узнав от ответчиков, короткий ответ: «Есть ли способ использовать метод расширения в блоке инициализатора объекта в 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");
    }

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

0 голосов
/ 25 сентября 2008

Возможно ли продление класса? Тогда вы можете легко добавить нужное вам свойство.

Если это не удастся, вы можете создать новый класс с похожими свойствами, который просто обратный вызов частного экземпляра интересующего вас класса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...