1

This is probably a stupid question, but just in case....

We have a 3rd party package with weird models like:

public partial class CountingDevice
{
    public int countingDeviceNo { get; set; }
    public string countingDeviceName { get; set; }
    public string obis { get; set; }
    public int integralPart { get; set; }
    public bool integralPartFieldSpecified;
    public int fractionalPart { get; set; }
    public bool fractionalPartFieldSpecified;
    public double value { get; set; }
    public bool valueFieldSpecified;
    public bool offPeakFlag { get; set; }
    public bool offPeakFlagFieldSpecified;
    public ExpectedMeterReading expectedMeterReading { get; set; }
    // snipped for brevity
}

You'll notice that sometimes there are pairs of fields like integralPart and integralPartFieldSpecified.

Here is the problem: If I simply assign some value to integralPart but do not set integralPartFieldSpecified = true, the value of integralPart will be completely ignored causing the solution to fail.

So when mapping our own models to this madness, I need to litter the code with constructs like:

if (IntegralPart != null)
{
    countingDevice.integralPartSpecified = true;
    countingDevice.integralPart = (int)IntegralPart!;
}

Both in the interest of reducing lines of code and not stumbling over a minefield, I would like to do any one of the following:

A. Overload the = operator so it will automatically check for a property which is a boolean and has "Specified" concatenated to the current property's name. If such a property exists, it will be assigned true when the value is assigned; if not, then assignment will operate as normal. Ideally, it should be "smart" enough to assign "...Specified" to false if the value assigned is null/default/empty.

B. Create some customer operator which will do the same as A.

C. Create some method which I could invoke in a concise and preferably typesafe way to do the same.

Is this possible? If so, how?

To make it clear: I need to build quite a few wrappers. I don't want to repeat this logic for every field and worry about missing some fields which it applies to. I want a generic way of assigning both fields at once if the "Specified" field exists and being able to do assignments in exactly the same way if it does not exist.

Brian Kessler
  • 2,187
  • 6
  • 28
  • 58
  • You cannot overload the "=" operator in C#. You might have some luck with an extension method for the model classes. – Mark Benningfield Feb 08 '23 at 13:08
  • @MakePeaceGreatAgain This seems to be from a third-party library, so adding members is not an option – PMF Feb 08 '23 at 13:08
  • Create some method that sets both properties then? Do you **need** to use `=`-operator? Or create your own model upon that 3rd-party one? – MakePeaceGreatAgain Feb 08 '23 at 13:09
  • 2
    @PMF: If that's the case then a 3rd party DTO not under the system's control shouldn't be used as a domain model. The system in question can maintain data internally however is needed, and just translate/map to this DTO in an encapsulated 3rd party integration. It seems like the core problem isn't operators and properties, but relying on a 3rd party component throughout the domain. – David Feb 08 '23 at 13:09
  • @David Agreed, if this is used throughout the system. But even if it is not, there's some place where one would need to map the data. – PMF Feb 08 '23 at 13:12
  • You could encapsulate the mess via a source generator? Producing something like `public int? integralPart { set { obj.integralPartFieldSpecified = value.HasValue; obj.integralPart = value ?? 0 } }`. Otherwise I'd hand write a set of extension methods... – Jeremy Lakeman Feb 09 '23 at 00:11

5 Answers5

3

not stumbling over a minefield

Encapsulate the minefield.

If you don't control this 3rd party DTO then don't use it throughout your domain. Encapsulate or wrap the integration of this 3rd party tool within a black box that you control. Then throughout your domain use your models.

Within the integration component for this 3rd party system, simply map to/from your Domain Models and this 3rd party DTO. So this one extra line of code which sets a second field on the DTO only exists in that one place.

David
  • 208,112
  • 36
  • 198
  • 279
  • Encapsulating the minefield is what I am trying to do. But it is a big minefield and I want to handle every variable in it in the same way, not needing to worry about whether I've missed a mine. – Brian Kessler Feb 08 '23 at 13:41
  • 1
    @BrianKessler: Overloading operators is most likely the wrong tool to do that. To continue the analogy... What you propose in the question is to walk through the minefield, but encapsulate one dangerous mine. Instead, encapsulate *the whole field*. What is this 3rd party integration and how is it used by your system? Can you wrap all of its integration within a service class? Or perhaps wrap this DTO in a custom model? Your code would interact with the wrapper, which would internally translate to/from this DTO. So this one value would need to be set in one place and nowhere else. – David Feb 08 '23 at 13:44
1

You cannot overload the = operator in C#.

You can just use custom properties and set the "FieldSpecified" fields in the setters e.g.

private int _integralPart;
public int integralPart 
{ 
    get { return _integralPart; }
    set
    {
        _integralPart = value;
        integralPartFieldSpecified = true;
    }
}
public bool integralPartFieldSpecified;

Update

If you want a generic solution you can use a generic class for properties that you want to achieve the specified behaviour with e.g.

public class ValueWithSpecifiedCheck<T>
{
    private T _fieldValue;

    public T FieldValue
    {
        get
        {
            return _fieldValue;
        }
        set
        {
            _fieldValue = value;
            FieldSpecified = true;
        }
    }

    public bool FieldSpecified { get; set; }
}

public class Data
{
    public ValueWithSpecifiedCheck<int> IntegralPart { get; set; }
}

Then the class/property would be used as following:

public static void Main()
{
    var data = new Data();

    data.IntegralPart = new ValueWithSpecifiedCheck<int>();
    data.IntegralPart.FieldValue = 7;
    Console.WriteLine(data.IntegralPart.FieldSpecified);// Prints true
}
YungDeiza
  • 3,128
  • 1
  • 7
  • 32
1

If you implement a generic solution and add implicit conversion operators, it's quite convenient to use.

Here's a sample Optional<T> struct (I made it a readonly struct to ensure immutable mechanics):

public readonly struct Optional<T> where T : struct
{
    public Optional(T value)
    {
        _value = value;
    }

    public static implicit operator T(Optional<T> opt) => opt.Value;
    public static implicit operator Optional<T>(T opt) => new(opt);

    public T Value => _value!.Value;

    public bool Specified => _value is not null;

    public override string ToString() => _value is null ? "<NONE>" : _value.ToString()!;

    readonly T? _value;
}

You could use that to implement your CountingDevice class like so:

public partial class CountingDevice
{
    public int              countingDeviceNo   { get; set; }
    public string           countingDeviceName { get; set; }
    public string           obis               { get; set; }
    public Optional<int>    integralPart       { get; set; }
    public Optional<int>    fractionalPart     { get; set; }
    public Optional<double> value              { get; set; }
    public Optional<bool>   offPeakFlag        { get; set; }
    // snipped for brevity
}

Usage is quite natural because of the implicit conversions:

public static void Main()
{
    var dev = new CountingDevice
    {
        integralPart = 10,      // Can initialise with the underlying type.
        value        = 123.456
    };

    Console.WriteLine(dev.fractionalPart.Specified);  // False
    Console.WriteLine(dev.integralPart.Specified);    // True
    Console.WriteLine(dev.value);                     // 123.456
    Console.WriteLine(dev.value.ToString());          // 123.456
    Console.WriteLine(dev.fractionalPart.ToString()); // "<NONE>"

    dev.fractionalPart = 42;  // Can set the value using int.
    Console.WriteLine(dev.fractionalPart.Specified);  // True
    Console.WriteLine(dev.fractionalPart);            // 42

    var optCopy = dev.offPeakFlag;
    Console.WriteLine(optCopy.Specified);             // False

    dev.offPeakFlag = true;
    Console.WriteLine(dev.offPeakFlag.Specified);     // True

    Console.WriteLine(optCopy.Specified); // Still False - not affected by the original.

    Console.WriteLine(optCopy); // Throws an exception because its not specified.
}

You might also want to use optional reference types, but to do that you will need to declare a generic with the class constraint:

public readonly struct OptionalRef<T> where T : class
{
    public OptionalRef(T value)
    {
        _value = value;
    }

    public static implicit operator T(OptionalRef<T> opt) => opt.Value;
    public static implicit operator OptionalRef<T>(T opt) => new(opt);

    public T Value => _value ?? throw new InvalidOperationException("Accessing an unspecified value.");

    public bool Specified => _value is not null;

    public override string ToString() => _value is null ? "<NONE>" : _value.ToString()!;

    readonly T? _value;
}

Personally, I think that's a bit overkill. I'd just use nullable value types, int?, double? etc, but it depends on the expected usage.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
1

Another (expensive) solution would be to write a method that takes in an object, a property name, and the new property value. You can then use reflection to both set the property value for the specified property, as well as search for the bool field that you want to set (if it exists).

Note that you need to pass the correct type for the property. There's no compile-time checking that you're passing a double instead of a string for the value property, for example.

Below I've created an extension method on the object type to simplify calling the method in our main code (the method becomes a member of the object itself):

public static class Extensions
{
    // Requires: using System.Reflection;
    public static bool SetPropertyAndSpecified(this object obj, 
        string propertyName, object propertyValue)
    {
        // Argument validation left to user

        // Check if 'obj' has specified 'propertyName' 
        // and set 'propertyValue' if it does
        PropertyInfo prop = obj.GetType().GetProperty(propertyName,
            BindingFlags.Public | BindingFlags.Instance);

        if (prop != null && prop.CanWrite)
        {
            prop.SetValue(obj, propertyValue, null);

            // Check for related "FieldSpecified" field 
            // and set it to 'true' if it exists
            obj.GetType().GetField($"{propertyName}FieldSpecified",
                BindingFlags.Public | BindingFlags.Instance)?.SetValue(obj, true);

            return true;
        }

        return false;
    }
}

After you add this class to your project, you can do something like:

static void Main(string[] args)
{
    var counter = new CountingDevice();

    // Note that 'valueFieldSpecified' and `integralPartFieldSpecified' 
    // are set to 'false' on 'counter'

    // Call our method to set some properties
    counter.SetPropertyAndSpecified(nameof(counter.integralPart), 42);
    counter.SetPropertyAndSpecified(nameof(counter.value), 69d);

    // Now 'valueFieldSpecified' and 'integralPartFieldSpecified' 
    // are set to 'true' on 'counter'
}
Rufus L
  • 36,127
  • 5
  • 30
  • 43
  • I think you are the only one who understands what I actually need! After leaving work, the same solution occured to me (almost character for character). Can we improve this in two ways? 1. Limit `this object obj` to members of some namespaces? and 2. pass some sort of expression to identify the field on the object, instead of using `string propertyName`? I realize compile timecheck for `object propertyValue` is a lost cause. – Brian Kessler Feb 08 '23 at 23:38
  • Sure, you can restrict the method to a specific type by changing `this object` to `this SomeOtherType`. – Rufus L Feb 08 '23 at 23:42
  • Maybe it is a topic for a new question, but there are many models which would need this, all from the same bunch of lunatics who have their own namespace. I don't want to restrict the extension to a single class, but to all classes in the namespace. – Brian Kessler Feb 09 '23 at 12:16
  • That's what I thought, which is why I used `object`. Extension methods apply to a *type*, not a namespace. If all the types have a common base, you could restrict it to *that* type. – Rufus L Feb 09 '23 at 16:02
  • Unfortunately there is no base type I can restrict such to. So far as I know, C# doesn't offer any way I can retrofit interfaces to third party models.... – Brian Kessler Feb 10 '23 at 10:41
0

C# doesn't allow overloading the = operator (unlike eg C++). However, your suggestion C should work. It's a bit of a hassle, too, since you'll have to write a bunch of methods, but you could write an extension method such as


public static class Extensions
{
    public static void UpdateIntegralPart(this CountingDevice dev, double value)
    {
        dev.integralPart = value;
        dev.integralPartSpecified = true;
    }
}

Then you can call

   countingDevice.UpdateIntegralPart(1234);
PMF
  • 14,535
  • 3
  • 23
  • 49
  • You seem to have missed the point that I am not just dealing with the integralPart, but many different fields and I want a generic way to deal with this rather than repeating logic. – Brian Kessler Feb 08 '23 at 13:42
  • 1
    @BrianKessler No, I saw that. But if you can't change the declaration and still need to use the type all over the place, there's no other option than to create extension methods. Maybe you could improve the duplication a bit by using reflection or the `dynamic` type, but neither of that will be type safe. – PMF Feb 08 '23 at 13:55