简体   繁体   中英

In C#, can I declare a property so that it can return just one of two types

I have a property (well, lots of them), that I currently have declared as object, but that I'd like to be able to restrict down to just two types. For example, I want a property that will either take a bool, or an instance of a class that contains an expression that will evaluate to a bool within the context that it is used (Expression class includes a string expression which it can parse at runtime and evaluate to a boolean, or error). Equally, I might do something similar with integer, double, DateTime etc.

How could I make a property that would only accept only a value or an instance of an Expression class that will produce that value?

I know that I may be asking something that C# won't natively do, but what would be the closest approximation?


Edit: For additional context, these are propeties of object that are being edited using a PropertyGrid in winforms, so that the user can dropdown to select one of {true/false/Expression}, where Expression opens a dialog for editing an Expression, or the user can type it into the PropertyGrid directly. For this reason, it would destroy the ergonomics to have two properties, as in the user's mind it will just be one.

Edit: For additional context, these are propeties of object that are being edited using a PropertyGrid in winforms, so that the user can dropdown to select one of {true/false/Expression}, where Expression opens a dialog for editing an Expression, or the user can type it into the PropertyGrid directly. For this reason, it would destroy the ergonomics to have two properties, as in the user's mind it will just be one.

The idiomatic solution in C# would be to create a dedicated class representing the possible choices, eg

class UserChoice
{
     public bool? BooleanValue { get; }
     public Expression ExpressionValue { get; }

     // The factory methods ensure that exactly one of BooleanValue
     // and ExpressionValue is not null.

     public static FromBoolean(bool value) => new UserChoice(value, null);

     public static FromExpression(Expression expression)
     {
         if (expression is null)
         {
             throw new ArgumentNullException(nameof(expression));
         }
         return new UserChoice(null, expression);
     }
     
     private UserChoice(bool? booleanValue, Expression expressionValue)
     {
         BooleanValue = booleanValue;
         ExpressionValue = expressionValue;
     }
}

This would be the basic structure. Possible improvements are:

  • Static UserChoice.True and UserChoice.False fields returned by FromBoolean . You now have reference equality for True/False values.
  • An Evaluate method that either returns the Boolean or evaluates the expression.
  • IsBoolean and IsExpression convenience properties.

Technically, you can achieve the behaviour with a help of implicit operator (see code below), but do you really want "two types" property? Isn't a better design to have two properties?

// Class that evaluates to bool
public class MyBoolClass {

  // Stab class which evaluates to constant bool
  public class MyBoolClassStub : MyBoolClass {
    internal MyBoolClassStub(bool value) {
      Value = value;
    }

    public bool Value { get; }

    public override bool ToBool() => Value;
  }

  // Stub class which returns true whenever evaluates to bool
  public static MyBoolClassStub EverTrue { get; } = new MyBoolClassStub(true);

  // Stub class which returns flase whenever evaluates to bool
  public static MyBoolClassStub EverFalse { get; } = new MyBoolClassStub(false);

  public virtual bool ToBool() {
    //TODO: here we evaluate to bool; put relevant code
  }

  public static implicit operator bool(MyBoolClass value) =>
    value?.ToBool() ?? throw new ArgumentNullException(nameof(value)); 

  public static implicit operator MyBoolClass(bool value) =>
    value ? EverTrue : EverFalse;
}

Having such a class declaration you can declare property as follow:

private MyBoolClass m_MyProperty;

public MyBoolClass MyProperty {
  get {
    if (m_MyProperty is null)
      return false; //TODO: default value here

    return m_MyProperty;
  }
  set {
    m_MyProperty = value;
  }
}

And use it

// We can assign bool (which will be turned into Stub class)
MyProperty = true;

// We can read bool
if (MyProperty)
  Console.WriteLine("true");

// We can assign "class evaluated to bool"
MyProperty = new MyClass();

Console.WriteLine(MyProperty ? "yes" : "no");

I think, your class has too many responsibilities. I mean one boolean property is enough. In my view, it is not really good idea to have such property which can be bool or Expression. It sounds like you want generic, but it is not generic because you want to apply result of Expression. So I think you need to evaluate this expression and then assign its value to bool variable.

I would create two classes. One class is for bool property and one class is for evaluation of expressions. Why? Because of Single Responsibility principle of SOLID principles.

So let me show an example:

class Foo 
{
    public bool MyBoolProperty { get; }

    public Foo(bool myBoolProperty)
    {
        MyBoolProperty = myBoolProperty;
    }
}

and:

class Expression 
{
    public Expression ExpressionValue { get; }

    public Expression(Expression expression)
    {
        ExpressionValue = expression;
    }
}

When you will have two classes, then it has many advantages:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM