Я бы согласился с большинством постов Джона - небольшим примечанием к словарю: с точки зрения производительности, вы можете захотеть сравнить это с простым списком. В прошлый раз, когда я делал эталонный тест (словарь против плоского списка, проверяя каждый элемент, пока не было найдено совпадение), точка разделения (для доступа на чтение) составляла около 150 элементов; ниже этого список был быстрее (просто через простоту). Но проведите свои собственные тесты ... (у меня нет цифр, чтобы доказать, так или иначе).
В зависимости от кода вы можете использовать обобщенные элементы для дальнейшего разделения данных - например, кэш-память, чтобы вся информация для типа T находилась в одном месте, заполняемая в статическом ctor для Cache. Это может или не может быть возможным, в зависимости от архитектуры.
Наконец, он может подходить или не подходить, но существуют существующие платформы AOP, такие как PostSharp, которые могут помочь упростить точки внедрения.
В общих словах - было бы довольно легко (в вашем коде инициализации) создать типизированный делегат для метода в методе Cache, чтобы уменьшить объем данных, через которые он должен сканировать - всего лишь часть Type. MakeGenericType и и Delegate.CreateDelegate - после этого момента код просто знает о вашем делегате Func <...> и ему не нужно заботиться о реализации.