开发者

Why has C# steered clear of this pattern? And how would you implement it instead?

开发者 https://www.devze.com 2023-01-17 06:27 出处:网络
SHORT FORM(!) In response the Jon Skeet\'s comment, what I want C# to be to do is to allow a generic, at compile time, to expand out and derive from one of it\'s generic parameters (as Kirk Woll demo

SHORT FORM(!)

In response the Jon Skeet's comment, what I want C# to be to do is to allow a generic, at compile time, to expand out and derive from one of it's generic parameters (as Kirk Woll demonstrates):

public class Generic<TBase> : TBase {

}

This class, of course, would not be able to override any members of TBase (unless perhaps if a constraint was applied) and would have to repeat all of TBase's public + protected constructors untouched - automatically forwarding the calls to the base.

The long form of the question shows an abstract + concrete class that presents a common pattern for implementing a given interface - but which, because C# doesn't allow multiple inheritance, cannot easily be applied on top of another base class without cloning all of the code and sticking a base class under the abstract - not exactly DRY!

LONG FORM

I know the technical reasons why the following code doesn't work - generics are run-time compiled, not compile-time templates - but I'm curious to know why the C# designers appear so reticent to go this way with the language (i've seen readability cited but struggle to understand that). I'd also like to know how you guys would implement this pattern.

I have an interface for an object that resolves dependencies via an IDependencyResolver:

public interface IDependant
{
  IDependencyResolver Resolver { get; set; }
}

I then have an abstract class

public abstract class DependantBase : IDependant
{
  #region IDependant Members

  public abstract IDependencyResolver Resolver
  {
    get; set; 
  }

  #endregion

  public virtual TDependency ResolveDependency<TDependency>(
    string name, params object[] args)
  {
    //null checks elided
    return Resolver.Resolve<TDependency>(name, args);
  }

  public TDependency ResolveDependency<TDependency>(
    params object[] args)
  {
    return ResolveDependency<TDependency>(null, args);
  }
}

And finally an instance class that simply materialises the Resolver property:

public class Dependant : DependantBase
{
  public override IDependencyResolver Resolver
  { get; set; }
}

So I can derive this type and override both the Resolver property, as well as how the resolve operation is actually carried out.

I've used it on a vanilla type (i.e. one that otherwise doesn't have a base) and then I come to write an MVC controller.

What I want to be able to do is to repeat the above two classes, but have them implemented as follows:

//'injects' the DependantBase functionality
//between TBase and the deriving class
public abstract class DependantBase<TBase> : TBase, IDependant
{
  //as DependantBase above
}

//and then have Dependant<TBase>:
public class Dependant : DependantBase<TBase>
{ 
  //as Dependant above
}

Thus, if the above were possible I could simply do this:

public class HomeController : Dependant<System.Web.Mvc.Controller>
{

}

And now HomeController derives from Dependant<System.Web.Mvc.Controller>, which in turn inherits from System.Web.Mvc.Controller.

The key here being that I want to preserve the previous pattern of being able to override both the property and the ResolveDependency helper method - but I don't want to have to keep cloning the same infrastructure code for each new branch of types (i.e. where a new pre-existing base type is required by some framework).

This is clearly not a generic in the .Net term because I'm not sure how you'd even begin to represent this in metadata - but the C# compiler could certainly treat this is as a template instead and expand it out at compile-time (god, it does it 开发者_StackOverflow中文版enough of this already with Expression Trees, DLR interfacing, in-line delegates, iterator blocks and auto get/set properties!).

The only way I can see of doing something like this is to push it up into a Visual Studio plugin (new language extension or something like that), or even a text template.


You could potentially accomplish your goals by using T4. This is already built into Visual Studio, and works with C# today.


Although C# generics are a step up from Java generics, in that objects of generic classes know their own types, and generic methods know the type parameters they were called with, generic type parameters do not participate in member bindings. Consider, for example:

public class Foo1
{
    virtual public int Bar() { return 23; }
}
public class Foo2 : Foo1
{
    override public int Bar() { return 47; }
}
public class Foo3 : Foo2
{
    new public string Bar() { return "Meow"; }
}
public static class Tester
{
    public void Test<T,U>(T param1, U param2) where T:Foo1 where U:Foo3
    {
        var a=param1.Bar();
        var b=param2.Bar();
        Debug.Print("{0} {1}",a,b);
    }
    public static void Test()
    {
      var thing1 = new Foo3();
      Test(thing1, thing1);
    }
}

Note that generic type parameters T and U of method Test will both be passed Foo3, but param1.Bar() will be bound to the version of that method which existed in Foo1 [the constraining type], while param2.Bar() will be bound to the version that existed in Foo3. Because the Foo1.Bar() is virtual and is overriden in Foo2, the call to param1.bar() will yield 47. Note that there is no mechanism by which the type of a can depend upon generic type T, so there's no way the compiler could possibly use Foo3.Bar().

If one considers that bindings cannot be performed based upon generic types, but only based upon the constraints placed on those types, it will be clear that inheriting from a generic type parameter can't possibly work, since one of the major functions of inheritance is to allow the derived class to use bindings from the parent. If Generic<Base> were to derive from Base, that would mean that Generic<Foo1> should have different bindings from Generic<Foo3>; such a feature is simply not available.

Incidentally, it would be theoretically possible, and in many cases useful, to implement a construct whereby a class Foo<T> would have a member (e.g. Joe) of some interface type T, and be regarded as implementing that interface. If Mark was of type Foo<IThingie>, then ((IThingie)Mark).DoSomething() would behave as Mark.Joe.DoSomething() [but without Mark having to publicly expose Joe]. That would take care of most of the cases where it would be helpful for a type to inherit from its generic parameters. It's possible to use Reflection to create types that behave that way while a program is running, but there's no way to specify such types statically.


So, basically, you want to wrap a type that needs dependencies in a generic class that will derive from its own generic type and implement a method that injects the dependencies into the base type. Obviously the TBase generic parameter as a parent is illegal; you cannot create a type with a parent that is not known at design-time. C# is a strong statically-typed language. Basically, what you want to do would require dynamic or "duck-typing"; the runtime would examine a potential use of an object, interrogate the object itself, and determine if the use is valid based on the "if it looks like a duck, quacks like a duck..." philosophy. C#4.0 can do this in certain cases using the new "dynamic" keyword, but "dynamic" should not be thrown around lightly as it pretty much disconnects all the type-checking the compiler can do for you.

If you're in love with using a generic for the purpose, you could get close to this by abstracting a base type to be used as a constraint of TBase:

public class DependantBase<TBase> : DependencyInjectableBase, IDependant where TBase: DependencyInjectableBase
{
...
}

Now DependantBase is both derived from and works with DependencyInjectableBase (which can be an empty "label" parent if there is no common functionality). However, you cannot declare a new DependantBase<ChildOfDependencyInjectableBase>() and treat the DependantBase instance as the derived ChildOfDependencyInjectableBase. This is a part of basic inheritance; a class cannot be treated as its "sibling" or "cousin".

The method provided by the C# language spec for "mixins" (functionality not defined in the class itself but that can be applied as if it were) is "extension methods"; public static methods that specify the first parameter as the type being extended using the "this" contextual keyword. You could implement such a method or methods on the types you want to inject dependencies into:

public static InjectDependencies(this Object dependant)
{
   //reflectively examine properties of dependant's true type for dependency types
   //the resolver knows how to inject
}

This could then be applied to any Object. The GetType() method is available on any .NET Object, and from there you can interrogate the types of properties or fields to find dependencies you can resolve.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号