Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you explain this generics behavior and if I have a workaround?

Sample program below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GenericsTest
{
    class Program
    {
        static void Main(string[] args)
        {
            IRetrievable<int, User> repo = new FakeRepository();

            Console.WriteLine(repo.Retrieve(35));
        }
    }

    class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    class FakeRepository : BaseRepository<User>, ICreatable<User>, IDeletable<User>, IRetrievable<int, User>
    {
        // why do I have to implement this here, instead of letting the
        // TKey generics implementation in the baseclass handle it?
        //public User Retrieve(int input)
        //{
        //    throw new NotImplementedException();
        //}
    }

    class BaseRepository<TPoco> where TPoco : class,new()
    {
        public virtual TPoco Create()
        {
            return new TPoco();
        }

        public virtual bool Delete(TPoco item)
        {
            return true;
        }

        public virtual TPoco Retrieve<TKey>(TKey input)
        {
            return null;
        }
    }

    interface ICreatable<TPoco> { TPoco Create(); }
    interface IDeletable<TPoco> { bool Delete(TPoco item); }
    interface IRetrievable<TKey, TPoco> { TPoco Retrieve(TKey input); }
}

This sample program represents the interfaces my actual program uses, and demonstrates the problem I'm having (commented out in FakeRepository). I would like for this method call to be generically handled by the base class (which in my real example is able to handle 95% of the cases given to it), allowing for overrides in the child classes by specifying the type of TKey explicitly. It doesn't seem to matter what parameter constraints I use for the IRetrievable, I can never get the method call to fall through to the base class.

Also, if anyone can see an alternate way to implement this kind of behavior and get the result I'm ultimately looking for, I would be very interested to see it.

Thoughts?

like image 264
Bryan Boettcher Avatar asked Oct 29 '12 16:10

Bryan Boettcher


2 Answers

That code doesn't compile for the same reason this simpler example doesn't compile:

public interface IBar
{
    void Foo(int i);
}

public class Bar : IBar
{
    public void Foo<T>(T i)
    {
    }
}

The methods simply don't have the same signature. Yes, you could call someBar.Foo(5) and it would resolve T to int, but the fact remains that Foo in Bar still doesn't have the same signature as a method that actually takes an int as a parameter.

You can further demonstrate this by having both a non-generic and generic method in the type; this doesn't result in an ambiguity related error:

public class Bar : IBar
{
    public void Foo(int i)
    {

    }
    public void Foo<T>(T i)
    {
    }
}

As for actually solving your problem, you could do this:

class FakeRepository : BaseRepository<User>, ICreatable<User>, IDeletable<User>, IRetrievable<int, User>
{
    public User Retrieve(int input)
    {
        return Retrieve<int>(input);
    }
}

This will mean that FakeRespository has both a generic and non-generic version of Retrieve, but in the end all calls are still directed to the generic version.

like image 195
Servy Avatar answered Oct 17 '22 01:10

Servy


The compiler has no idea what TKey is in BaseRepository and has no way of relating this to IRetreivable (note that generic methods do not have the same signature as non-generic ones).

I think you want something more along these lines, with the base class doing the interface inheriting, and to also specify the TKey:

class FakeRepository : BaseRepository<int, User>
{
}

class BaseRepository<TKey, TPoco> : ICreatable<TPoco>, IDeletable<TPoco>, IRetrievable<TKey, TPoco> where TPoco : class,new()
{
    public virtual TPoco Create()
    {
        return new TPoco();
    }

    public virtual bool Delete(TPoco item)
    {
        return true;
    }

    public virtual TPoco Retrieve<TKey>(TKey input)
    {
        return null;
    }
}
like image 25
lc. Avatar answered Oct 17 '22 03:10

lc.