Prisoner ZERO
Prisoner ZERO

Reputation: 14166

Why can't I return my object as its' interface?

I have a series of interface definitions, all of which compile (so my objects are composed correctly). The objects instantiate, as expected. However, when I try to return the object from its' underlying factory I get the following error:

ERROR:

Unable to cast object of type 'SampleLibrary.Domain.DataAcessors.Person.SQLDataAccessor' to type 'Common.Contracts.DataAccessors.IDataAccessorModel`2[SampleLibrary.Contracts.Models.IPerson,SampleLibrary.Domain.DataAccessors.Types.SqlServer]'.

Please keep in mind I am trying to return each instance as the IDataAccessor interface.

CODE:

public interface IDataAccessor<I, T>
{
    T AccessType { get; }
}

public interface IDataAccessorModel<I, T> : IDataAccessor<I, T>
{
    I Instance { get; }

    IResult<string> Get(I instance);
    IResult<string> Add(I instance);
    IResult<string> Update(I instance);
    IResult<string> Delete(I instance);
}

public class SQLDataAccessor : IDataAccessorModel<IPerson, IAccessType>
{
    internal SQLDataAccessor(IResult<string> result)
    {
        _connectionString = "";
        _result = result;
    }

    private readonly string _connectionString;
    private IResult<string> _result;

    public IAccessType AccessType { get { return new SqlServer(); } }
    public IPerson Instance { get; private set; }

    public IResult<string> Add(IPerson instance)
    {
        Instance = instance;
        return _result;
    }
    public IResult<string> Get(IPerson instance)
    {
        Instance = instance;
        return _result;
    }
    public IResult<string> Delete(IPerson instance)
    {
        Instance = instance;
        return _result;
    }
    public IResult<string> Update(IPerson instance)
    {
        Instance = instance;
        return _result;
    }
}

public class FactoryDataAccess : IFactoryDataAccess
{
    internal FactoryDataAccess() { }

    public IDataAccessor<I, T> Create<I, T>()
    {
        var model = typeof(I);
        var target = typeof(T);

        if (model.IsAssignableFrom(typeof(IPerson)))
        {
            if (target == typeof(SqlServer)) {

                var accessor = new Person.SQLDataAccessor(new Result());

                // This next line FAILS!
                return (IDataAccessorModel<I, T>)accessor;
            }
        }

        throw new NotSupportedException("Type " + target.FullName + " and Source " + model.FullName + " is not supported.");
    }
}

UPDATE:
Please keep in mind that IDataAccessorModel can be used by any desired DataAccess type you wish to define.

Upvotes: 0

Views: 154

Answers (4)

Olivier Jacot-Descombes
Olivier Jacot-Descombes

Reputation: 112299

It work for me if I declare SQLDataAccessor like this:

public class SQLDataAccessor : IDataAccessorModel<IPerson, SqlServer>
{
    ...
}

You are probably calling it like this

var factory = new FactoryDataAccess();
var da = factory.Create<IPerson, SqlServer>();

i.e. you call it with T being SqlServer. If you declare T as IAccessType in SQLDataAccessor it is not guaranteed that IAccessType would be SqlServer. Therefore the casting error. (However, SqlServer is guaranteed to be IAccessType as it probably implements it.)

Upvotes: 0

mhusaini
mhusaini

Reputation: 1252

The way you have it, I could be any type derived from IPerson and T is exactly of type SqlServer, which would cause the cast to fail since SQLDataAccessor implements the IDataAccessorModel with different parameters. You would need to have a more exact cast, such as:

return (IDataAccessorModel<IPerson, IAccessType>)accessor;

Upvotes: 0

Amiram Korach
Amiram Korach

Reputation: 13286

SQLDataAccessor is not a generic class, but implements IDataAccessorModel<IPerson, IAccessType> exactly, so your method Create should return IDataAccessor<IPerson, IAccessType>, but you probably called it with other generic types.

Change SqlDataAccessor to:

public class SQLDataAccessor<I, T> : IDataAccessorModel<I, T>
{
    internal SQLDataAccessor(IResult<string> result)
    {
        _connectionString = "";
        _result = result;
    }

    private readonly string _connectionString;
    private IResult<string> _result;

    public T AccessType { get { return new SqlServer(); } }
    public I Instance { get; private set; }

    public IResult<string> Add(I instance)
    {
        Instance = instance;
        return _result;
    }
    public IResult<string> Get(I instance)
    {
        Instance = instance;
        return _result;
    }
    public IResult<string> Delete(I instance)
    {
        Instance = instance;
        return _result;
    }
    public IResult<string> Update(I instance)
    {
        Instance = instance;
        return _result;
    }
}

You might want to limit I and T to the interfaces, so add a where constraint:

public class SQLDataAccessor<I, T> : IDataAccessorModel<I, T> 
    where I : IPerson
    where T : IAccessType

Upvotes: 0

Thomas Levesque
Thomas Levesque

Reputation: 292405

SQLDataAccessor implements IDataAccessorModel<IPerson, IAccessType>, so it would work only if <I, T> were <IPerson, IAccessType>. There is no guarantee about that, since the method is generic and I and T could be any type, so the cast fails.

Of course, since you're checking the types of I and T, you know the cast would be valid, but the compiler doesn't. You can trick it like this:

return (IDataAccessorModel<I, T>)(object)accessor;

However, since T has to be SqlServer, it doesn't make sense to make it a generic type parameter. And since I has to implement IPerson, there should be a constraint on it. So the method signature should be:

public IDataAccessor<I, T> Create<T>() where T : IPerson

Upvotes: 1

Related Questions