|
using System; |
|
using System.Collections.Generic; |
|
|
|
public class ExampleUsage |
|
{ |
|
public void Elided() |
|
{ |
|
// Let's say you have a ton of objects inheriting from the same class or interface ... |
|
var myObjects = new List<ICommonInterface>(); |
|
|
|
// You want to call a method over each one of them but are worried about the cost those virtual calls would introduce ... |
|
foreach (var myAbstractObject in myObjects) |
|
myAbstractObject.DoTheThing(10); // This would go through the VTable to find the right method to call, and also prevents any inlining |
|
|
|
// You can instead break up each object by type ... |
|
var eliders = new Dictionary<Type, Elider>(); |
|
foreach (var myAbstractObject in myObjects) |
|
Elider.AddToHandlers(myAbstractObject, eliders); // ... and force the JIT to create one new type per type instance |
|
|
|
// Then it's just a case of having a purpose built Elider for each case you may need, |
|
// This sounds ripe for abstraction, sadly you can't really abstract as you would want because generalizing |
|
// this prevents the JIT/PGO from inlining appropriately, leading to worse performance than the naive version. |
|
|
|
Elider.DoTheThing(eliders); |
|
} |
|
} |
|
|
|
public interface ICommonInterface |
|
{ |
|
public int DoTheThing(int i); |
|
|
|
public static int MyInt; |
|
} |
|
|
|
public sealed class ConcreteA : ICommonInterface { public int DoTheThing(int i) => ICommonInterface.MyInt += i + 1; } |
|
public sealed class ConcreteB : ICommonInterface { public int DoTheThing(int i) => ICommonInterface.MyInt += i + 2; } |
|
public sealed class ConcreteC : ICommonInterface { public int DoTheThing(int i) => ICommonInterface.MyInt += i + 3; } |
|
|
|
public abstract class Elider |
|
{ |
|
protected abstract void Add(ICommonInterface obj); |
|
protected abstract bool Remove(ICommonInterface obj); |
|
protected abstract void DoTheThing(); |
|
|
|
public static void AddToHandlers(ICommonInterface item, Dictionary<Type, Elider> handlers) |
|
{ |
|
var concreteType = item.GetType(); |
|
if (handlers.TryGetValue(concreteType, out var batcher) == false) |
|
{ |
|
var gen = typeof(Handler<>).MakeGenericType(concreteType); // Create one specific Handler<> for this concrete type |
|
batcher = (Elider)Activator.CreateInstance(gen)!; // Create an instance of this specialized type to pass our items to it |
|
handlers.Add(concreteType, batcher); |
|
} |
|
batcher.Add(item); |
|
} |
|
|
|
public static bool RemoveFromHandlers(ICommonInterface item, Dictionary<Type, Elider> handlers) |
|
{ |
|
return handlers.TryGetValue(item.GetType(), out var handler) && handler.Remove(item); |
|
} |
|
|
|
public static void DoTheThing(Dictionary<Type, Elider> handlers) |
|
{ |
|
foreach (var kvp in handlers) |
|
kvp.Value.DoTheThing(); |
|
} |
|
|
|
private sealed class Handler<T> : Elider where T : ICommonInterface // This class get specialized to a concrete type |
|
{ |
|
private List<T> _abstraction = []; |
|
protected override void Add(ICommonInterface obj) => _abstraction.Add((T)obj); |
|
protected override bool Remove(ICommonInterface obj) => _abstraction.Remove((T)obj); |
|
protected override void DoTheThing() |
|
{ |
|
foreach (var abstraction in _abstraction) |
|
abstraction.DoTheThing(10); |
|
} |
|
} |
|
} |