My example below involves 2 NET classes which both contain the method CommonMethod. I would like to design MyMethod that can accept either class (Using ) while retaining the functionality common to NetClassA and NetClassB. Case1 would do just that only it is illegal as stated below. Case2 would also accomplish the goal except INetClassA and INetClassB do not exist. Therefore my question is there a way to impose a custom interface (ICommonNetMethods) on existing .NET types (Case 3)? Alternative solutions to my problem are welcomed.
// Case 1: Illegal because "where" can only have 1 base 开发者_JS百科class
public void MyMethod<Ttype>(Ttype myClass) where Ttype : NetClassA, NetClassB {}
// Case 2: Legal to utlize multiple "where" interface types
public void MyMethod<Ttype>(Ttype myClass) where Ttype : INetClassA, INetClassB {}
// Case 3: For this to work ICommonNetMethods must be added to NetClassA/NetClassB
public void MyMethod<Ttype>(Ttype myClass) where Ttype : ICommonNetMethods {}
NetClassA() { This .NET class has method CommonMethod() }
NetClassB() { This .NET class has method CommonMethod() }
interface ICommonNetMethods { void CommonMethod() }
Thanks, aidesigner
There are ways to solve this that involve creative thinking.
Most obvious:
Adapter Pattern
You build your interface, then two adapters where each take NetClassA and the other NetClassB. Your common code stays common and the specific lives in the adapters.
This works even for sealed classes. You do not dervice from NetClassA or NetClassB. I kind of want to leave this to you to figure out the implementation, come back in a day if you want the code implementation I'll post it.
Other things to look at:
Extension Methods
and/or
Reflection
More Help
=====================
= ICommonNetMethods =
=====================
| (derive)
|-------------------------------|
==================== ====================
= NetClassAAdapter = = NetClassBAdapter =
==================== ====================
| uses (not derive) | uses (not derive)
============= =============
= NetClassA = = NetClassB =
============= =============
Use Func<>
:
Assume two classes, A and B, each with a function Foo
(though this isn't really a requirement for this solution, observe class C, below):
public class A { int Foo() { return 1; } }
public class B { int Foo() { return 2; } }
public class C { int Deviant() { return 3; } }
Then in some code fragment, you will write:
var a = new A();
var b = new B();
var c = new C();
var fs = new Func<int>[] {() => a.Foo(), () => b.Foo(), () => c.Deviant()};
So to use this:
foreach(var func in fs)
Console.WriteLine(func());
Which in turn will output:
1
2
3
Lambda functions are a big deal in C#, and a great technology to learn. If you are unfamiliar, and would like to learn more, start at Microsoft's help page.
If you are looking at larger interfaces, consider, as has been mentioned, the adapter pattern. If the idea of wrapping each of your objects with their own concrete adapter classes seems like too much bloat for your buck, then again, Func<> to the rescue.
public interface ISomeInterface
{
void f1();
int f2(string p1);
...
}
public class FuncImplementation : ISomeInterface
{
public Action Func_f1 { get; set; }
public Func<string,int> Func_f2 { get; set; }
...
public void f1() { Func_f1(); }
public int f2(string p1) { return Func_f2(p1); }
...
}
Now you can make new Adapters inline:
var adaptA = new FuncImplementation { Func_f1 = MyF1, Func_f2 = Myf2 };
adaptA.f1();
You cannot impose an interface on existing code (unless you use a code weaver like PostSharp, but that's cheating ;-).
Instead, consider these options:
- If you simply have a single method on your interface, you could use a Delegate instead.
- You could make a simple wrapper class for each of your types, and implement the interface there.
C# 4.0 introduced the dynamic
keyword which allows C# developers to use duck typing (an alternative to the adapter pattern). With it, you could define MyMethod
like this:
public void MyMethod(dynamic myClass)
{
myClass.CommonMethod();
}
You could then simply pass instances of NetClassA and NetClassB to MyMethod
like this:
var a = new NetClassA();
var b = new NetClassB();
MyMethod(a);
MyMethod(b);
The drawback to this approach is that there's no static type checking. If NetClassA or NetClassB didn't have a method called CommonMethod
that accepted no parameters, the program would compile, but fail at run time.
Also since there's no associated interface, it's not clear what functions and properties are available. Avoid using this approach in public facing assemblies.
The only way I can think of (off the top of my head) is to derive from the .NET class in question and add your interface to that implementation. I don't think that's the optimal solution, however.
Why not simply inspect the type that Ttype is in the method, and execute your code accordingly based on the type?
For example:
public void MyMethod<Ttype>(Ttype myClass)
{
string className = typeof(Ttype).Name;
switch (className)
{
case "NetClassA":
// Do stuff
break;
case "NetClassB":
// Do stuff
break;
default:
// Do something if necessary
break;
}
}
Thanks to all, I was really impressed with the various options. First I had already started pursing the delegate option ( The use of nested type parameters and recursion (C#) ) and have an almost ideal solution. The second post on this thread shows my exact implementation. This approach tries to solve the problem by passing just the needed function "Add" of NETClassA (SrgsItem) and NetClassB (SrgsElement) instead of the entire class. This is almost perfect except C# lack of "Generics Variance" support is getting in the way.
As to the other options they are all very insightful. After pursuing the delegate thread I will be trying the Adapter/Func approach proposed by Michael and Andrew (Will add comments). If you have time please follow the delegate thread above as it relates and it might help understand another facet of C#.
As of 2022, the best practice of C# is still to map external classes into Value Objects or Adaptors. To some people such as me, this is a logic overhead I wish to remove.
C# type system is closed in that we cannot extend an existing class with new interfaces. Of course, this can be mitigated by using a New-type Pattern.
class ExternalClass {
public string InfoWithDifferentLayoutOrName { get; }
}
interface IMyInterface {
string Info { get; }
}
record struct ExternalClassExtensionWrapper(ExternalClass Value): IMyInterface {
public string Info => Value.InfoWithDifferentLayoutOrName;
}
T MyAwesomeInnerFunc<T>(T input) where T: IMyInterface { ... }
But, from the view of code design, this approach does not cut down on code logic compared to a value-object mapper as you still have to write something like a wrapper. The only difference is whether you are depending on a concrete layout (VOs) or a contract (interfaces). A mysophobia do exist in the wild that insists interfaces bring lower coupling, but I don't see any lower cognitive burden in this specific case.
You will like a trait system where you can extend interfaces on others.
精彩评论