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?

200_success
145k22 gold badges190 silver badges478 bronze badges
asked May 12, 2019 at 15:04
\$\endgroup\$
2
  • 1
    \$\begingroup\$ What's the definition of the IConstrainedSetter interface ? \$\endgroup\$ Commented May 13, 2019 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\$ Commented May 14, 2019 at 17:11

1 Answer 1

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.
answered May 13, 2019 at 7:17
\$\endgroup\$
4
  • \$\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\$ Commented May 13, 2019 at 11:04
  • \$\begingroup\$ @BanMe you could create a mutable builder and once an instance is created from it then forever. \$\endgroup\$ Commented May 13, 2019 at 11:07
  • \$\begingroup\$ hello, small query.. Do I just continually repost the code after making modifications? \$\endgroup\$ Commented May 14, 2019 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\$ Commented May 14, 2019 at 13:01

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.