Home > Back-end >  C# Constructor Optional Parameters Values Change On Instantiation
C# Constructor Optional Parameters Values Change On Instantiation

Time:09-23

I am attempting a WPF app in C# to learn. I believe I have a ViewModel (SolarSystemViewModel) bound to the XAML window, which creates a new instance of a model (SolarSystemModel) I have two contructors on my model, and when I call this from my ViewModel, I get an unexpected value for the optional parameter, (despite explicitly setting it to false, the new instance has the value as true). Other options are working as expected (ie flag options setting / resetting depending on passed in parameters). If I don't include any optional params, it also works as expected.

For example I set the data context like so:

public partial class MainWindow : Window
{
   public MainWindow()
   {
       InitializeComponent();
       this.DataContext = new SolarSystemViewModel(20M, null, groundMounted: false);
   }
}

And in my ViewModel constructor(s):

public SolarSystemViewModel(decimal systemSize, decimal? batterySize)
        {
            _solarSystem = new SolarSystemModel(systemSize, batterySize);
            UpdateCommand = new SolarSystemModelUpdateCommand(this);
        }

        public SolarSystemViewModel(
            decimal systemSize,
            decimal? batterySize,
            bool wiFiWebOption = true,
            bool threePhaseOption = true,
            bool optosOption = true,
            bool smartMeterOption = false,
            bool galvOrAlFrames = false,
            bool highRoofOrDifficultAccess = false,
            bool groundMounted = false,
            bool structuralRoofEng = true,
            bool cyclonicFramesEng = false,
            bool basicCommercialAppro = true,
            decimal batteryInverterCableLength = 5)
        {
            _solarSystem = new SolarSystemModel(
                systemSize,
                batterySize,
                wiFiWebOption,
                optosOption,
                smartMeterOption,
                galvOrAlFrames,
                highRoofOrDifficultAccess,
                groundMounted,
                structuralRoofEng,
                cyclonicFramesEng,
                basicCommercialAppro
            );

            _solarSystem.BatteryCableLength = batteryInverterCableLength;

            UpdateCommand = new SolarSystemModelUpdateCommand(this);
        }

Then the actual model constructors:

public SolarSystemModel(
            decimal systemSize,
            decimal? batterySize
        )
        {
            SystemSize = systemSize;
            _inverterSize = _inverterFactor * SystemSize;
            _batterySize = batterySize;
            _batteryInverterSize = batterySize / _batteryInverterFactor;

            // Some sensible defaults...
            WiFiOption = true;
            ThreePhaseOption = true;
            OptosOption = true;
            SmartMeterOption = false;
            GalvOrAlFramesOption = false;
            HighRoofOrDifficultAccessOption = false;
            GroundMountOption = false;
            StructuralRoofEngOption = true;
            CyclonicFramesEngOption = false;
            BasicCommercialApproOption = true;
            BatteryCableLength = 5;
            this.CalculateCosts();
            OnPropertyChanged("Cost");
        }

        public SolarSystemModel(
            decimal systemSize, 
            decimal? batterySize, 
            bool wiFiWebOption = true, 
            bool threePhaseOption = true, 
            bool optosOption = true, 
            bool smartMeterOption = false,
            bool galvOrAlFrames = false,
            bool highRoofOrDifficultAccess = false,
            bool groundMounted = false,
            bool structuralRoofEng = true,
            bool cyclonicFramesEng = false,
            bool basicCommercialAppro = true,
            decimal batteryInverterCableLength = 5
        )
        {
            SystemSize = systemSize;
            _inverterSize = _inverterFactor * SystemSize;
            _batterySize = batterySize;
            _batteryInverterSize = batterySize / _batteryInverterFactor;

            WiFiOption = wiFiWebOption;
            ThreePhaseOption = threePhaseOption;
            OptosOption = optosOption;
            SmartMeterOption = smartMeterOption;
            GalvOrAlFramesOption = galvOrAlFrames;
            HighRoofOrDifficultAccessOption = highRoofOrDifficultAccess;
            GroundMountOption = groundMounted;
            StructuralRoofEngOption = structuralRoofEng;
            CyclonicFramesEngOption = cyclonicFramesEng;
            BasicCommercialApproOption = basicCommercialAppro;
            BatteryCableLength = batteryInverterCableLength;
            this.CalculateCosts();
            OnPropertyChanged("Cost");
        }

The property that is returning true despite the false-set parameter looks like:

public bool GroundMountOption
        {
            get
            {
                if ((_systemOptions & SolarOptions.GroundMount) > 0)
                    return true;
                return false;
            }

            set
            {
                if (value == true)
                    _systemOptions |= SolarOptions.GroundMount;
                else
                    _systemOptions &= ~SolarOptions.GroundMount;
            }
        }

...where I have an enum (bitwise flags) for the options:

[Flags]
    public enum SolarOptions
    {
        ThreePhase = 1,
        WiFiWeb = 2,
        SmartMeter = 4,
        Optos = 8,
        GalvOrAlFrames = 16,
        HighRoofOrDifficultAccess = 32,
        GroundMount = 64,
        StructuralRoofEng = 128,
        CyclonicFramesEng = 256,
        BasicCommercialAppro = 512
    }

The XAML bindings look like:

<StackPanel Grid.Row="3">
            <Label FontWeight="Bold">Installation Options:</Label>
            <CheckBox IsChecked="{Binding SolarSystem.ThreePhaseOption}">Three-phase</CheckBox>
            <CheckBox IsChecked="{Binding SolarSystem.WiFiOption}">Wifi / Web</CheckBox>
            <CheckBox IsChecked="{Binding SolarSystem.SmartMeterOption}">Smart meter</CheckBox>
            <CheckBox IsChecked="{Binding SolarSystem.OptosOption}">Optimisers</CheckBox>
            <CheckBox IsChecked="{Binding SolarSystem.GalvOrAlFramesOption}">Galv / Al Frames</CheckBox>
            <CheckBox IsChecked="{Binding SolarSystem.HighRoofOrDifficultAccessOption}">High Roof / Difficult Access</CheckBox>
            <CheckBox IsChecked="{Binding SolarSystem.GroundMountOption}">Ground Mount</CheckBox>
            <CheckBox IsChecked="{Binding SolarSystem.StructuralRoofEngOption}">Structural Roof Engineering</CheckBox>
            <CheckBox IsChecked="{Binding SolarSystem.CyclonicFramesEngOption}">Cyclonic Frames Engineering</CheckBox>
            <CheckBox IsChecked="{Binding SolarSystem.BasicCommercialApproOption}">Basic commercial approvals / engineering</CheckBox>
            <StackPanel Orientation="Horizontal">
                <Label>Battery-Inverter Cable Length:</Label>
                <TextBox Text="{Binding SolarSystem.BatteryCableLength}" Width="200" VerticalAlignment="Center"></TextBox>
            </StackPanel>
            <TextBlock Text="{Binding Path=SolarSystem.Cost, StringFormat=C}"></TextBlock>
        </StackPanel>

When I break and step through the code, the groundMounted parameter is false at the ViewModel constructor, but when the actual model is instantiated (eg The SolarSystemModel), the groundMount parameter is shown as true on instantiation. I am at a loss has to how to troubleshoot this, or what the cause could be. Any help would be welcome because clearly I'm missing something fundamental, which could come down to my attempt at MVVM implementation. I haven't had much experience with non-console apps.

Screen Example

CodePudding user response:

As pointed out by @Joe in the comments, this problem was a result of the ViewModel constructor missing an argument being passed to the Model constructor.

This is as a direct result of having a constructor with so many parameters. As suggested by @Clemens and @BionicCode, it would be

better [to] use object initializers,

or even create an Options class to encapsulate the configuration as suggested by @BionicCode.

  • Related