Search code examples
c#.netdelegatesdesktop-application

When I set an object using an Action<> the object assigned is always null


My goal is create an Action<> for set the configuration object in the constructor.

The Action<> that I have defined, set two configuration objects by reference, but the problem is that the assigned object is always null. I thought that internally, the objects, were assigned by reference but it seems that no.

In the example code, I have created a CarConfiguration in the main program and I try to set by reference this configuration to my new Car using an Action<> which defines the assignment between the main program CarConfiguration and the Car attribute configuration.

Why my car configuration attribute is always null even though is being assigned by reference in my Action<> method?

Main Class:

CarConfiguration carConfiguration = new CarConfiguration()
{
    CarName = "Ferrari",
    CarModel = "LaFerrari",
    Spolier = true,
    BuildDate = new DateTime(2018, 01, 01)
};

//Thats not work because the "conf" parameter is never assign in the Car constructor
Car myOwnCar = new Car(conf => 
{
    conf = carConfiguration;
});
Console.WriteLine(myOwnCar.CarConfigurationText());

//That works, but is not my purpose do it like this!
Car myOtherCar = new Car(carConfiguration);
Console.WriteLine(myOtherCar.CarConfigurationText());

Configuration Class:

public class CarConfiguration
{
    public bool Spolier { get; set; } = false;
    public string CarName { get; set; } = String.Empty;
    public string CarModel { get; set; } = String.Empty;
    public DateTime BuildDate { get; set; } = default(DateTime);
}

Car Class:

public class Car
{
    private CarConfiguration carConfiguration = null;

    //That does not work because carConfiguration is not assigned in the Action as a reference
    public Car(Action<CarConfiguration> configureCar)
    {
        configureCar(carConfiguration);
    }

    //That works!
    public Car(CarConfiguration configureCar)
    {
        carConfiguration = configureCar;
    }

    public string CarConfigurationText()
    {
        StringBuilder strBuilder = new StringBuilder();

        if (carConfiguration != null)
        {
            strBuilder.AppendLine(carConfiguration.CarModel);
            strBuilder.AppendLine(carConfiguration.CarName);
            strBuilder.AppendLine(carConfiguration.Spolier.ToString());
            strBuilder.AppendLine(carConfiguration.BuildDate.ToString("mm-DD-yyyy"));
        }
        else
        {
            strBuilder.AppendLine("Car is not configure!");
        }

        return strBuilder.ToString();
    }
}

Link output example


Solution

  • Your action is assigning the configuration to the lambda parameter which has no effect as the parameter is an input value, it does not get returned out of the lambda. Objects are passed by reference, but references are copied to parameters in function calls, i.e. conf will receive a copy of the reference to carConfiguration when you call the action like so configureCar(carConfiguration);

    Assigning and overwriting this local copy of the reference serves no purpose. You have to use the ref keyword to essentially pass a reference to the reference(variable) to the object. When assigning to a variable marked with ref it will overwrite the reference held in the original member variable and not the local variable inside the lambda. This is already demonstrated in the other answer.

    The proper method to implement what you are trying to accomplish is not by passing references around but by configuring the object inside the action. If you want to use an existing configuration just pass the object as you've already done so. There is no need to write an action that accepts a reference to an object in an explicit manner.

    public Car(Action<CarConfiguration> configureCar)
    {
      carConfiguration = new CarConfiguration();
      configureCar(carConfiguration);
    }
    
    // This is the common configuration pattern seen in .NET
    Car myOwnCar = new Car(conf => 
    {
        conf.CarName = "Ferrari";
        conf.CarModel = "LaFerrari"
        /** etc **/
    });
    

    If you want to copy values from an existing configuration, you can write a method to do so

    public static class CarConfigurationExtensions
    {
      public static void CopyTo(CarConfiguration this source, CarConfiguration dest){
        dest.CarName = source.CarName;
        dest.CarModel = source.CarModel;
        // etc
      }
    }
    
    
    Car myOwnCar = new Car(conf => carConfiguration.CopyTo(conf));
    

    But under no circumstance is writing an action that accepts a ref to a local variable a thing. Another alternative is to use a Func<CarConfiguration> like so, maybe if you want to do a lazy intialization.

    public Car(Func<CarConfiguration> configurator)
    {
      _configurator = configurator;
    }
    
    private Func<CarConfiguration> _configurator;
    private CarConfiguration _carConfiguration;
    
    public CarConfiguration CarConfiguration => 
      _carConfiguration ?? (_carConfiguration = _configurator());
    
    Car myOwnCar = new Car(() => carConfiguration);
    

    Note how the configuration is instantiated and stored- the first time it is accessed, perhaps the only time accepting a function in the constructor is useful.