4
\$\begingroup\$

The below code is aimed at providing you the most amount of control and flexibility with control which value gets set to a property.

I Introduce to you, the ConstrainedSetter<T> Generic Class. This class Takes a input type, creates a private field to represent that type, and provides a framework for controlling how that field is set, It also provides a method for logging failures, changing what the error messages say, optionally taking action on success or failure, optionally modifying the input, optionally validating the input to see if meets the criteria setup, and finally with no ModifyAction Function and PreCheck Predicate setup it acts as a normal setter.

[Serializable]
public class ConstrainedSetter<T> : IConstrainedSetter<T>
{
    public int HashId = default;
    public ConstrainedSetter()
    {
    }


    #region logging
    public ILogger Logger { get; set; }
    public Action FailAction { get; set; } = default;
    public string pcnmLogFail { get; set; }
    public string pcmLogFail { get; set; }
    #endregion

    #region ControlFlow
    public Predicate<T> CheckBeforeSet { get; set; } = default;
    public Func<T,T> ModifyBeforeSet { get; set; } = default;
    public Action SuccessAfterSet { get; set; } = default;
    #endregion


    private T data = default;
    public T Data { get => data; set => data = Set(value); }

    public virtual T ModifyInput(T value)
    {
        return ModifyBeforeSet(value);
    }
    public virtual void Log(string Message)
    {
        Logger.Log(Message);
    }
    private T Set(T _value)
    {
        if (ModifyBeforeSet == default)
        {
            if (CheckBeforeSet != default)
            {
                if (CheckBeforeSet(_value) == true)
                {
                    if (SuccessAfterSet != default)
                    {
                        SuccessAfterSet();
                    }
                    return _value;
                }
                if (Logger != default)
                {
                    Log(pcnmLogFail);
                }
                if (FailAction != default)
                {
                    FailAction();
                }
                return default;
            }
            return _value;
        }
        else
        {
            T Local = ModifyInput(_value);
            if (CheckBeforeSet != default)
            {
                if (CheckBeforeSet(Local) == true)
                {
                    if (SuccessAfterSet != default)
                    {
                        SuccessAfterSet();
                    }
                    return Local;
                }
                if (Logger != default)
                {
                    Log(pcmLogFail);
                }
                if (FailAction != default)
                {
                    FailAction();
                }
                return default;
            }
            return _value;
        }

    }
}
public static class ConstrainedSetterExtensions
{
    public static ConstrainedSetter<T> Setup<T>(this ConstrainedSetter<T> set,Action _logFailAction = default, string _preCheckNoModify = default, string _preCheckModify = default)
    {

        set.FailAction = _logFailAction;

        set.pcnmLogFail = _preCheckNoModify;

        set.pcmLogFail = _preCheckModify;

        return set;
    }
    public static ConstrainedSetter<T> Setup<T>(this ConstrainedSetter<T> set, Func<T,T> _modifyAction = default, Predicate<T> _preCheck = default, Action _successAction = default)
    {

        set.ModifyBeforeSet = _modifyAction;

        set.CheckBeforeSet = _preCheck;

        set.SuccessAfterSet = _successAction;

        set.HashId = HashCode.Combine(set.Data, set.SuccessAfterSet, set.CheckBeforeSet, set.ModifyBeforeSet);

        return set;
    }

    public static ConstrainedSetter<T> Clone<T>(this ConstrainedSetter<T> ObjSource )
    {
        using (var ms = new MemoryStream())
        {
            var formatter = new BinaryFormatter();
            formatter.Serialize(ms, ObjSource);
            ms.Position = 0;
            return (ConstrainedSetter<T>)formatter.Deserialize(ms);
        }
    }
    public static ConstrainedSetter<T> CreateNewType<T>(this ConstrainedSetter<T> Source)
    {
        return new ConstrainedSetter<T>();
    }
}

So rather then explaining it further, I will show my current test for it.

This test demonstrates the creation of a Int where I add every option to the setter for the Data Property.

The define Failure callback and the success callback. Both do nothing in this implementation, but they can do whatever you want them to do. I then Create the type I want, add my logging service to it, and call setup to add the failure callback and the failures strings. The next setup call, adds the modify action, the precheck predicate and the SuccessCallback.

    public ConstrainedSetterTests()
    {
        void FailureCallBack()
        {
        }
        void SuccessCallBack()
        {
        }
        ConstrainedSetter<int> programmable = new ConstrainedSetter<int>();
        programmable.Logger = new Logger();
        programmable.Setup(FailureCallBack, "Prechecker routine failed", "Precheck after modification failed")
        .Setup(_modifyAction: x => { return x = x + 1; }, x => x == 100, SuccessCallBack);
        programmable.Data = 98;
    }

The result of this test is that the data is set to 0 and the log for pcmLogFail is received by the logger and failure callback is run.

Any thoughts on improvements or anything really helps. What can I do to improve my post, and what can I do to make it more generic?

\$\endgroup\$
  • 1
    \$\begingroup\$ What's the definition of the IConstrainedSetter interface ? \$\endgroup\$ – Adrian Iftode May 13 at 5:36
  • \$\begingroup\$ Sorry about this adrian, but for this version there way no interface definition, in Other words IConstrainedSetter<T> was blank. \$\endgroup\$ – BanMe May 14 at 17:11
4
\$\begingroup\$

This might actually be useful in some cases, however, we first need to get rid of its weaknesses and clean-up the code a little bit:

  • Every property of ConstrainedSetter is mutable, so it can be modified by anyone, anytime. This is not good because it allows me to override your settings whenever I desire. This should definitely be immutable.
  • Both the FailureCallBack and SuccessCallBack should pass the instance of the ConstrainedSetter as a parameter so that one has the necessary context about what went wrong or what succeeded. The former should also pass the reason it didn't work.
  • ILogger should not be part of this class. There are already two callbacks to handle failures and successes. Let the user handle this. Adding a logger makes it redundant.
  • Your naming convention here is exactly the other way around. Names prefixed with an _ underscore are usually private fields and not public parameters. Similar, lowercase properties should not be public and abbreviating them to a magic pc[n]m is a no-go.
  • I cannot figure out what the HashId is for. You should document it better.
\$\endgroup\$
  • \$\begingroup\$ Thank you for the guidance, I really hope this does become useful., I was thinking about this mutability issue today actually,,.if I do set a private read only field or property to bring about immutability, then the class would need more internal functions as input parameter handlers, or more parameters in the constructor, if I don't want more functions. is there another way to approach this that doesn't lead to this scenario? \$\endgroup\$ – BanMe May 13 at 11:04
  • \$\begingroup\$ @BanMe you could create a mutable builder and once an instance is created from it then forever. \$\endgroup\$ – t3chb0t May 13 at 11:07
  • \$\begingroup\$ hello, small query.. Do I just continually repost the code after making modifications? \$\endgroup\$ – BanMe May 14 at 12:41
  • \$\begingroup\$ @BanMe you can ask a new question when you have something new to review or a self-answer if you just want to share the latest version. The question's code must remain unchanged after reviews are posted. \$\endgroup\$ – t3chb0t May 14 at 13:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.