简体   繁体   中英

Allow derived type cast as parent type to return `object`, but using the derived type's method

Given the following code (which I know won't compile and isn't correct):

public abstract class Fetcher {
   public abstract IEnumerable<object> Fetch();
}

public class Fetcher<T> : Fetcher {
   private readonly Func<IEnumerable<T>> _fetcher;
   public Fetcher(Func<IEnumerable<T>> fetcher) { _fetcher = fetcher; }
   public IEnumerable<T> Fetch() => _fetcher(); // override or new
}

And this example setup:

var myFetchers = new List<Fetcher> {
   new Fetcher<string>(() => new List<string> { "white", "black" })),
   new Fetcher<int>(() => new List<int> { 1, 2 }))
};

How can I structure my code so that this will work?

IEnumerable<IEnumerable<object>> fetcherResults =
   myFetchers.Select(fetcher => fetcher.Fetch()); // get objects from derived method

TLDR; I've thought about this a little more. A different way to state my question is: how do I run the Fetch method on all the items in the myFetchers list and save a collection of their results, without having to determine the base type of each item and do a cast to that type, while not returning IEnumerable<object> from the Fetch method itself?

The key here is that at the time the objects of different generic type are in a collection of the parent type, I want to run the derived type's method, but return the result as IEnumerable<object> , not IEnumerable<T> . (This can be done using a different method if necessary, I suppose, though it would be nice if it were the same name.) This is so that a section of the code that doesn't need to know about fetchers can have its data (and just be passed fetcherResults ), but the part of the code that does care about fetchers can completely ignore what type of data the fetchers work with (not its concern, only the consumer of the data needs to work with the specific types).

I don't have to use inheritance here, and interface IFetcher would work just as well. I keep thinking about how explicit interface implementation might help, but I'm just not closing the loop right now how to make this work for me:

// in a class implementing ICollection<T>
public IEnumerator<T> GetEnumerator() => _collection.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable) _collection).GetEnumerator();

As usual, the real situation is more complicated than this (such as the data the fetchers are providing coming from a database, etc.). But getting this one part down will be all I need.

I have looked at the following resources to no avail: implement an inheritable method that returns an object , call method on generic type from abstract parent .

There is a straight-forward way to do this with explicit implementation as you suspected. This is only available if you use an interface rather than an abstract class.

public interface IFetcher {
    IEnumerable<object> Fetch();
}

public class Fetcher<T> : IFetcher  {
    private readonly Func<IEnumerable<T>> _fetcher;
    public Fetcher(Func<IEnumerable<T>> fetcher) { _fetcher = fetcher; }
    IEnumerable<object> IFetcher.Fetch() => Fetch().Cast<object>();
    public IEnumerable<T> Fetch() => _fetcher();
}

In this implementation, the interface version of the call returns IEnumerable<object> , while the class-accessible version returns the more specific IEnumerable<T> . If you call the fetcher through the interface, it will resolve to the first. Calling through the class will resolve to the second.

If I understand the question correctly, then you can solve the problem by making Fetcher<T> implement a generic interface IFetcher<T> like this:

public interface IFetcher<T>
{
    IEnumerable<T> Fetch();
}

public class Fetcher<T> : IFetcher<T>
{
    private readonly Func<IEnumerable<T>> _fetcher;
    public Fetcher(Func<IEnumerable<T>> fetcher) { _fetcher = fetcher; }
    public IEnumerable<T> Fetch() => _fetcher();
}

And then creating an adapter that can convert IFetcher<TFrom> to an IFetcher<TTo> like this:

public class Adaptor<TFrom, TTo> : IFetcher<TTo>
{
    private readonly IFetcher<TFrom> _fetcher;

    public Adaptor(IFetcher<TFrom> fetcher)
    {
        _fetcher = fetcher;
    }

    public IEnumerable<TTo> Fetch()
    {
        return _fetcher.Fetch().Cast<TTo>();
    }
}

This allows for example to convert from IFetcher<string> to IFetcher<object> .

With the help of this extension method:

public static class ExtensionMethods
{
    public static Converter<TFrom> Convert<TFrom>(this IFetcher<TFrom> fetcher)
    {
        return new Converter<TFrom>(fetcher);
    }

    public class Converter<TFrom>
    {
        private readonly IFetcher<TFrom> _fetcher;

        public Converter(IFetcher<TFrom> fetcher)
        {
            _fetcher = fetcher;
        }

        public IFetcher<TTo> To<TTo>()
        {
            return new Adaptor<TFrom, TTo>(_fetcher);
        }
    }
}

You can do something like this easily:

static void Main(string[] args)
{
    var myFetchers = new List<IFetcher<object>>
    {
       new Fetcher<string>(() => new List<string> { "white", "black" }).Convert().To<object>(),
       new Fetcher<int>(() => new List<int> { 1, 2 }).Convert().To<object>()
    };

    var results = myFetchers.Select(x => x.Fetch());
}

Please note that you can do without the extension method, but it will make your code more readable.

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