Proxy
A class that functions as an interface to a particular resource. That resource may be remote, expensive to construct, or may be require logging or some other added functionality.
Protection proxy
A protection proxy adds additional validation as a wapper class for the original.
Original apis
Let Car
as a api without validation. And Driver
as the thing to invoke the functionality.
interface ICar
{
void StartEngine();
}
class Car : ICar
{
public void StartEngine() => Console.WriteLine("Car is being driven...");
}
record class Driver(int Age);
2
3
4
5
6
7
8
9
Additional validation
class CarProxy(Car car, Driver driver) : ICar
{
public void StartEngine()
{
if (driver.Age > 16)
{
car.StartEngine();
}
}
}
2
3
4
5
6
7
8
9
10
Property Proxy
A property proxy has additional validation to check whether the new value is equal to the old, only update when true. This approach avoids unnecessary reassignment of a property.
What we should implement
If you're developing a grid control, we could have it with property RowCount
, and we should able to assign value to this wrapped property.
Grid g = new() { RowCount = new(2) };
class Grid : Control
{
private Property<int> _rowCount = new();
public int RowCount
{
get => _rowCount.Value;
set => _rowCount.Value = value;
}
}
2
3
4
5
6
7
8
9
10
11
INFO
The current only way to use a property proxy in a class in .NET
is make it private field and expose it with a T
property, or the mechanism won't work.
Property wrapper should have features like:
- reassignment check
- implicit conversion from
Property<T>
toT
and back - equatable implementation and operators
Wrap the property
class Property<T>(T value) : IEquatable<Property<T>>, IEqualityOperators<Property<T>, Property<T>, bool>
where T : new()
{
private T _value = value;
public T Value
{
get => _value;
set
{
if (Equals(_value, value)) return;
Console.WriteLine($"Assigning value as {value}");
_value = value;
}
}
public Property() : this(new T()) { }
public bool Equals(Property<T>? other)
{
if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
return EqualityComparer<T>.Default.Equals(_value, other.Value);
}
public static bool operator ==(Property<T>? left, Property<T>? right)
{
if (left is null || right is null) return false;
return left.Equals(right);
}
public static bool operator !=(Property<T>? left, Property<T>? right) => !(left == right);
public override int GetHashCode() => HashCode.Combine(_value);
public override bool Equals(object? obj)
{
if (obj is null) return false;
if (obj is not Property<T>) return false;
return Equals(obj as Property<T>);
}
public static implicit operator Property<T>(T value) => new(value);
public static implicit operator T(Property<T> property) => property.Value;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Grid g = new() { RowCount = 1 }; // output: Assigning value as 1
g.RowCount = 2; // output: Assigning value as 2
g.RowCount = 2; // no output
2
3
Value Proxy
Value proxy is simply a wrapper for value types, especially for primitive types like int
, float
.
Simple value proxy
What very simple value proxy is a wrapper of a value type with implicit conversion operators.
Price<int> p = 123;
Console.WriteLine($"Price is {(int)p}"); // Price is 123
readonly struct Price<T>(T value) where T : INumber<T>
{
private readonly T _value = value;
public static implicit operator Price<T>(T value) => new(value);
public static implicit operator T(Price<T> price) => price._value;
}
2
3
4
5
6
7
8
9
10
However this can be approached by using
alias and extension method(if extra functionalities are required), but alias information only available in assembly scope.(Non-generic version though.)
using Price = int; // This alias is not visible outside the assembly.
static class NumberExtension
{
public static void DoSomething<T>(this Price number) { }
}
2
3
4
5
Percentage Proxy
Thing should be implemented
var p = 2.Percentage() + 3.Percentage(); // 5%
var p1 = 20 + 30.Percentage();
var p2 = 2 - 50f.Percentage();
var p3 = 4 * 25d.Percentage();
var p4 = 1 / 66m.Percentage();
var p11 = 30.Percentage() + 20;
var p12 = 2f.Percentage() - 50;
var p13 = 4d.Percentage() * 25;
var p14 = 1m.Percentage() / 66;
2
3
4
5
6
7
8
9
Solution
Solution can be easily achieved using operator overloading and extension methods. It's worth noting that, to avoid precision lose of integer types like int
and short
, as far as I can tell, there's no way around with INumber<T>
. So, separated extension methods are needed if you want to implement it for integer types.
readonly struct Percentage<T> where T : struct, INumber<T>
{
private readonly T _value;
internal Percentage(T value) => _value = value;
public static T operator +(T left, Percentage<T> right) => left + right._value;
public static T operator -(T left, Percentage<T> right) => left - right._value;
public static T operator *(T left, Percentage<T> right) => left * right._value;
public static T operator /(T left, Percentage<T> right) => left / right._value;
public static Percentage<T> operator +(Percentage<T> left, T right) => new(left._value + right);
public static Percentage<T> operator -(Percentage<T> left, T right) => new(left._value - right);
public static Percentage<T> operator *(Percentage<T> left, T right) => new(left._value * right);
public static Percentage<T> operator /(Percentage<T> left, T right) => new(left._value / right);
public static Percentage<T> operator +(Percentage<T> left, Percentage<T> right) => new(left._value + right._value);
public static Percentage<T> operator -(Percentage<T> left, Percentage<T> right) => new(left._value - right._value);
public static Percentage<T> operator *(Percentage<T> left, Percentage<T> right) => new(left._value * right._value);
public static Percentage<T> operator /(Percentage<T> left, Percentage<T> right) => new(left._value / right._value);
public override string ToString()
{
return _value switch
{
int v => $"{v * 100}%",
float f => $"{f * 100}%",
double d => $"{d * 100}%",
decimal m => $"{m * 100}%",
_ => base.ToString() ?? nameof(Percentage<T>)
};
}
}
static class PercentageExtension
{
public static Percentage<decimal> Percentage(this int number) => new(number / 100m);
public static Percentage<T> Percentage<T>(this T number)
where T : struct, INumber<T>
{
return number switch
{
float f => new(T.CreateChecked(f / 100)),
double d => new(T.CreateChecked(d / 100)),
decimal m => new(T.CreateChecked(m / 100)),
_ => throw new NotImplementedException($"Solution for type: {{{typeof(T).Name}}} is not implemented.")
};
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Composite Proxy - Aos/SoA
In some scenarios we do enumerations to modify values of a collection of certain structured type. Assuming we are developing a game, Enemy
is an entity model and we can perform magic on an collection of them.
An array of entity model Entity
can be called as Array of Structure(AoS)
.
var enemies = Enumerable.Range(1, 100)
.Select(_ => new Enemy() { Age = Random.Shared.Next(18, 36), Agility = Random.Shared.NextDouble() })
.ToArray();
Magic.Freeze(enemies);
class Enemy
{
public int Age { get; set; }
public double Agility { get; set; }
}
static class Magic
{
public static void Freeze(params Enemy[] enemies)
{
foreach (var e in enemies) e.Agility = 0;
}
public static void Freeze(IEnumerable<Enemy> enemies)
{
foreach (var e in enemies) e.Agility = 0;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Problem of AoS
When iterating over a field of all elements (e.g., updating all Agility
), the memory access pattern can be non-contiguous, leading to cache misses and reduced performance.
Solution - SoA