I am trying to do something like the following -
public abstract class Utility<A, B>
{
// Return values cannot be cached in class-scoped fields as these need to be invoked by threaded application
public abstract A GetPrimaryData(float foo);
public abstract B GetSecondaryData(float bar, A primaryData);
public abstract float ProcessData(A primaryData, B secondaryData);
}
public class SpecificUtility : Utility<C, D>
{
public override C GetPrimaryData(float foo) { /* implementation */ }
public override D GetSecondaryData(float bar, C primaryData) { /* implementation */ }
public override float ProcessData(C primaryData, D secondaryData) { /* implementation */ }
}
public class UserClass
{
// Important - this script doesn't care what A or B is! Should be able to use ANY implementation of Utility.
// As a result, we can't define UserClass as a generic of <A, B>
public Utility<A, B> Utility;
// This can be invoked in parallel for many tables concurrently
public void UseCase(Table table)
{
// For performance reasons, retrieving utilPrimaryData in the inner loop is not feasible
A utilPrimaryData = Utility.GetPrimaryData(GetFoo(table));
var stuffUserCaresAbout = table.AsParallel().ForAll(row =>
{
B utilSecondaryData = Utility.GetSecondaryData(GetBar(row), utilPrimaryData);
return Utility.ProcessData(utilPrimaryData, utilSecondaryData);
});
Consume(stuffUserCaresAbout);
}
}
I was thinking of making an IUtility interface that Utility class implements, and the UserClass interacts with, but that doesn't actually address the issue because the UserClass needs to actually obtain the PrimaryData and SecondaryData that the Utility will then process into the value the User needs to work with.
The only solution I can think of would be to make GetPrimaryData and GetSecondaryData return generic Objects which would then be casted into the specific types that the Utility subclasses are expecting, but I find that very unsatisfying and suspect the performance implications would not be great either.
You can use
dynamic:Just be aware that you'll lose all type safety for these calls, turning any mistakes with method names, etc. into a runtime error. To reduce the surface area of possible mistakes, you could extract most of this to a generic method that gets dynamically invoked:
You may want to consider moving this code into a context that does care about the generic types, though. For example, this code has a Feature Envy smell that might be addressed by giving
Utilitya method that handles these details with a non-generic signature: