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?