简体   繁体   中英

Return concrete type in abstract class

We have an abstract class BaseClass (note generic arg!) with a method called me. Me returns this.

If we use Me in the concrete classes we will get a return type object. Then we have to cast the result of Me to the type we originally are working with.

How can we achieve that Me returns the actual type of this? In this example type A?

public abstract class BaseClass<TIdentifier>{
 public virtual object Me{ get { return this; } }
}

public class A: BaseClass<long>
{

}

public class B: BaseClass<long>
{

}

public class controller{
   public void SomeMethod(){
       var a = new A();
       var b = new B();

       var aObject = a.Me; // this will be of type object
       var aObjectCasted = (A)aObject; // cast to original

       // How I want it
       var aConcrete = a.Me; // this returns type a
   }
}

Update

Since some people really, desperately (wink:-)) wish to understand what I'm actually trying to do.

With NHibernate we are doing this:

var result = Session.Get<A>(idToLookUp);

In some cases it happens that result isn't of type A but is of type AProxy, due to laze loading etc. Now if we want to cast result to something else: we will get an invalidcastexception because the actual type of result isn't A but AProxy. And that type can't be casted. We can only cast type A to the other type.

A workaround for this is described here: http://sessionfactory.blogspot.be/2010/08/hacking-lazy-loaded-inheritance.html . That's where the Me property in the above examples comes in.

So to get result of type A and not of type AProxy we now have to do this:

var result = (A)Session.Get<A>(idToLookUp).Me;

Note we have to cast me back to type A if we want to get to read and know the property of result.

My question: can we get rid of the casting and adjust the Me property so we instantly return the concrete type?

Hope it's clear now.

You can change the return type of this property to the definition of parent class

public abstract class BaseClass<TIdentifier> 
{
     public virtual BaseClass<TIdentifier> Me{ get { return this; } }
}

If you want to return exactly the same class you can make some workaround by adding the result type in the generic type parameter

public abstract class BaseClass<TIdentifier, TMe>
    where TMe : BaseClass<TIdentifier, TMe>, new()
{
    public virtual TMe Me { get { return (TMe)this; } }
}

public class A : BaseClass<long, A>
{

}

You could use an interface on your derived classes:

public interface IStrongTypedMe<T>
{
    T Me();
}

Your derived classes would become:

public class A: BaseClass<long>, IStrongTypedMe<A>
{
    public new A Me()
    {
        return base.Me() as A;
    }
}

This is assuming you can change A , of course.

Update:

I understand the issue now (only had time to read the linked article now).

Try using an extension method to do the casting for you like this:

    public static TReturnType As<TReturnType,TIdentifier>(this BaseClass<TIdentifier> proxyObject)
        where TReturnType : class
    {
        return proxyObject.Me as TReturnType;
    }

And you'd use it like:

var result = Session.Get<A>(idToLookUp).As<A,long>();

No changes to A or B required.

Unfortunately, C#, unlike Java , does not support return type covariance . Otherwise you could just override the property Me in the subclasses like this to get what you want:

public abstract class BaseClass<TIdentifier> {
    public virtual object Me { get { return this; } }
}

public class A: BaseClass<long>
{
    public override A Me { get { return this; } } // wont work in C#
}

public class B: BaseClass<long>
{
    public override B Me { get { return this; } } // wont work in C#
}

Mikhail Neofitov provides a good workaround though.

In order to do something like this:

var aObject = A.Me();

Me will need to be a static method.

  1. A static method doesn't have a this .
  2. If your not using a static method, you have the this - otherwise how are you willing to call the class method? You just need to cast it to the correct type.

You have this code:

var a = new A();
var aObject = a.Me;

Now what are you expecting here?
You have a which is from type A .
By using var you can't have multiple different return types from the Me geter.

The problem seems to be the implicit definition of the variable using var . When you are using var in this case, the compiler cannot determine the correct type for aObject in the editor. So take the following code for example:

public abstract class BaseClass<TIdentifier>
{
    public virtual object Me {get {return this;} }
}

public class A : BaseClass<TIdentifier>
{
    public int X
    {
        get {return 1;}
    }
}

public class B : BaseClass<TIdentifier>
{

}

public class controller{
    public void SomeMethod(){
        var a = new A();
        var b = new B();

        var aObject = a.Me; 
        var aObjectCasted = (A)aObject;

        // the environment cannot determine the correct type for aObject
        // without compiling and running. At this time in the editor,
        // this will be recognized as a type object. It will not
        // understand aObject.X and will not compile
        Console.WriteLine(aObject.X); 

        // During run-time, this will work. aObject will be defined as type A
        Console.WriteLine(aObject.GetType().GetProperty("X").GetValue(aObject));

        // this will output A for the type
        Console.WriteLine(aObject.GetType());
   }
}

Without being able to modify A and B, using the GetProperty, GetMethod, etc. methods on the implicitly defined variable seems like it will be your only hope.

Update: You can reference this to see the types of calls you can make on a Type object. It seems like you will have to do this more dynamically that desired to achieve the functionality you want. The object will not be defined correctly before compiling if trying to do it implicitly.

var aConcrete = a.Me; in your code will indeed return yield a type A for aConcrete at compile time, but not in the editor.

From MSDN : "It is important to understand that the var keyword does not mean "variant" and does not indicate that the variable is loosely typed, or late-bound. It just means that the compiler determines and assigns the most appropriate type ."

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM