C#中如何对无共同接口的动态类型进行静态类型检查?
Great question! When you're stuck with external classes you can't modify that share a method signature but don't implement a common interface, using dynamic works but exposes you to annoying runtime errors. Let's cover two solid approaches to add static type checking here:
1. Interface + Adapter Wrappers (Recommended: True Compile-Time Checking)
This approach gives you strict compile-time validation by creating a common interface and wrapping your external types to fit it—no modifications to the original classes needed.
First, define an interface that matches the shared method signature:
public interface IHasError { string getError(); }
Next, create adapter wrappers for each external class, plus implicit operators to let the compiler automatically convert instances of A/B to IHasError:
public static class ErrorAdapters { // Wrapper for class A private class AErrorWrapper : IHasError { private readonly A _instance; public AErrorWrapper(A instance) => _instance = instance; public string getError() => _instance.getError(); } // Wrapper for class B private class BErrorWrapper : IHasError { private readonly B _instance; public BErrorWrapper(B instance) => _instance = instance; public string getError() => _instance.getError(); } // Implicit conversion for A → IHasError public static implicit operator IHasError(A instance) { return new AErrorWrapper(instance); } // Implicit conversion for B → IHasError public static implicit operator IHasError(B instance) { return new BErrorWrapper(instance); } }
Now rewrite your utility function to accept IHasError instead of dynamic:
void printError(IHasError obj) { Console.WriteLine("Error: {0}", obj.getError()); }
How it works:
- When you pass an instance of
AorBtoprintError, the compiler automatically uses the implicit conversion to wrap it in an adapter. - If you try to pass a class
C(without agetErrormethod), the compiler will throw an error immediately—no runtime surprises.
2. Generic + Pre-Flight Type Validation (Lightweight, Pseudo Compile-Time)
If you want to avoid writing wrappers, this approach uses generics and reflection to validate types once (on first use) instead of letting errors slip to runtime. It's not strict compile-time checking, but it catches issues much earlier than raw dynamic.
public static class ErrorPrinter { // Cache valid types to avoid repeated reflection checks private static readonly HashSet<Type> _validatedTypes = new HashSet<Type>(); static ErrorPrinter() { // Pre-validate your known good types upfront ValidateType<A>(); ValidateType<B>(); } private static void ValidateType<T>() { var getErrorMethod = typeof(T).GetMethod("getError", Type.EmptyTypes); if (getErrorMethod == null || getErrorMethod.ReturnType != typeof(string)) { throw new InvalidOperationException( $"Type {typeof(T)} doesn't implement the required string getError() method." ); } _validatedTypes.Add(typeof(T)); } public static void PrintError<T>(T obj) { if (!_validatedTypes.Contains(typeof(T))) { ValidateType<T>(); } Console.WriteLine("Error: {0}", obj.getError()); } }
How it works:
- Known valid types (
A/B) are validated once during static constructor initialization. - If you pass a bad type like
C, the first call toPrintError<C>will throw an exception immediately, rather than failing mid-execution later.
内容的提问来源于stack exchange,提问作者Jake Cobb

