views:

255

answers:

6

The simple demo below captures what I am trying to do. In the real program, I have to use the object initialiser block since it is reading a list in a LINQ to SQl select expression, and there is a value that that I want to read off the database and store on the object, but the object doesn't have a simple property that I can set for that value. Instead it has an XML data store.

It looks like I can't call an extension method in the object initialiser block, and that I can't attach a property using extension methods.

So am I out of luck with this approach? The only alternative seems to be to persuade the owner of the base class to modify it for this scenario.

I have an existing solution where I subclass BaseDataObject, but this has problems too that don't show up in this simple example. The objects are persisted and restored as BaseDataObject - the casts and tests would get complex.

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) };
}
}

One of the answers (from mattlant) suggests using a fluent interface style extension method. 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);

But will this work in a LINQ query?

+4  A: 

Object Initializers are just syntactic sugar that requires a clever compiler, and as of the current implementation you can't call methods in the initializer.

var x = new BaseDataObject { SomeValue = 3, SomeOtherValue = 4 };

Will get compiler to something like this:

BaseDataObject tempObject = new BaseDataObject();
tempObject.SomeValue = 3;
tempObject.SomeOtherValue = 4;
BaseDataObject x = tempObject;

The difference is that there can't be any synchronization issues. The variable x get's assigned the fully assigned BaseDataObject at once, you can't mess with the object during it's initialization.

You could just call the extension method after the object creation:

var x = new BaseDataObject { SomeValue = 3, SomeOtherValue = 4 };
x.SetBarValue()

You could change SetBarValue to be a property with get/set that can be assigned during initialization:

public int BarValue
{
    set
    {
        //Value should be ignored
    }
}

Or, you could subclass / use the facade pattern to add the method onto your object:

public class DataObjectWithBarValue : BaseDataObject
{
    public void BarValue
    {
        set
        {
            SetData("bar", value);
        }
        get
        {
            (int) GetData("bar");
        }
    }
}
Tigraine
+4  A: 

No but you could do this....:

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

ANd have your extension return the object like Linq Does.

EDIT: This was a good thought untill i reread and saw that the base class was developed by a third person: aka you dont have the code. Others here have posted a correct solution.

mattlant
Must be nice to have the high rep points to just go and delete your wrong answer rather than to own up to your mistake :/ (You know who you are ;))
mattlant
It should still work. LINQ providers only care about the part of a Select query where the mapped members are referenced.
Mark Cidade
I deleted mine because I it was a duplicate of yours.
Mark Cidade
so the fact that SetBarValue returns void does not make a difference?
mattlant
I don't understand your "EDIT". It looks like this might work.
Anthony
@mattlant oh right, I didn't notice the void
Mark Cidade
+3  A: 

Even better:

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

and you can use this extension method for derived types of BaseDataObject to chain methods without casts and preserve the real type when inferred into a var field or anonymous type.

Pop Catalin
A: 

Is extending the class a possibility? Then you could easily add the property you need.

Failing that, you can create a new class that has similar properties that simply call back to a private instance of the class you are interested in.

Guvante
+1  A: 
 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);
Mark Cidade
A: 

Right, having learned from the answerers, the short answer to "Is there any way to use an extension method in an object initializer block in C#?" is "No."

The way that I eventually solved the problem that I faced (similar, but more complex that the toy problem that I posed here) was a hybrid approach, as follows:

I created a subclass, e.g.

public class SubClassedDataObject : BaseDataObject
{
    public int Bar
    {
        get { return (int)GetData("bar"); }
        set { SetData("bar", value); }
    }
}

Which works fine in LINQ, the initialisation block looking like

    SubClassedDataObject testSub = new SubClassedDataObject
    { SomeValue = 3, SomeOtherValue = 4, Bar = 5 };

But the reason that I didn't like this approach in the first place is that these objects are put into XML and come back out as BaseDataObject, and casting back was going to be an annoyance, an unnecessary data copy, and would put two copies of the same object in play.

In the rest of the code, I ignored the subclasses and used extension methods:

    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");
    }

And it works nicely.

Anthony