0

Can someone explain why the following does not work?

Here are my declarations:

public interface ITableData
{
    DateTimeOffset? CreatedAt { get; set; }
    bool Deleted { get; set; }
    string Id { get; set; }
    DateTimeOffset? UpdatedAt { get; set; }
    byte[] Version { get; set; }
}

public interface IUser : ITableData
{
    string Name { get; set; }
}

public abstract class TableData : ITableData
{
    public DateTimeOffset? CreatedAt { get; set; }
    public bool Deleted { get; set; }
    public string Id { get; set; }
    public DateTimeOffset? UpdatedAt { get; set; }
    public byte[] Version { get; set; }
}

public class User : TableData, IUser
{
    public User() { }

    public string Name { get; set; }
}

Now in my generic repository, I declare a sync table like so:

public abstract class BaseTypedRepository<T> : ITypedRepository<T> where T : ITableData
{
    ....
    public BaseTypedRepository(...) 
        : base()
    {
        SyncTable = Client.GetSyncTable<T>();
    }
}

And UserRespository inherits BaseTypedRepository like this:

public class UserRepository : BaseTypedRepository<User>, IUserRepository
{...}

So now I understand that UserRepository has a SyncTable of type User defined. and User implements ITableData. So why is the following not working:

IMobileServiceSyncTable<ITableData> tbl = userRepository.SyncTable;

How can I treat each repository's SyncTable as a generic IMobileServiceSyncTable of ITableData?

Edit 1 By "not working" I mean that the line above will not compile due to "Cannot implicitly convert IMobileServicesSyncTable to IMobileServicesSyncTable. An explicit conversion exists. Are you missing a cast?".

When I do:

IMobileServiceSyncTable<ITableData> tbl = repository.SyncTable as IMobileServiceSyncTable<ITableData>;

The line compiles but after I run the code (even after adding class to BaseTypedRepository constraints), tbl is null.

IMobileServiceSyncTable is part of Microsoft Mobile Client SDK found here (https://github.com/Azure/azure-mobile-services/blob/master/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Table/Sync/IMobileServiceSyncTable.Generic.cs).

Igor
  • 569
  • 5
  • 24
  • What exactly does "not working" mean? Please post the compiler-error (if it is one) or the exception message (if it's "not working" at run-time). How is `SyncTable` declared? There will of course be a compiler error if `SyncTable` isn't assignable at compile-time. – René Vogt Jan 17 '18 at 17:17
  • Maybe you just need to add a `class` constraint to the generic delcaration of `BaseTypedRepository`. [Variance is not supported for value-types](https://stackoverflow.com/a/12454932/5528593) – René Vogt Jan 17 '18 at 17:20
  • @RenéVogt, see Edit 1 above. – Igor Jan 17 '18 at 17:49

1 Answers1

0

For that to work, IMobileServiceSyncTable<T> would have to be declared as IMobileServiceSyncTable<out T> but it isn't.

I am not so confident with what this interface does, but given that it represents a table, it likely does both read and write accesses. For such an interface, you cannot use covariance or contravariance. Read accesses (returning the type parameter as method return type or readonly property type) only allows you to use covariance (the out keyword) while write accesses only allow you to use contravariance (the in keyword).

So long story short, what you have to do is create your own wrapper interface that only performs read access and mark this interface covariant. However, if you do that, there is actually little advantage over just creating a dedicated interface that is already specific to ITableData elements. Otherwise, you can shift the type information to the client, i.e. make the client generic and put the type parameter there. Then, it could be OK to return a IMobileServiceSyncTable<User> instead of IMobileServiceSyncTable<ITableData>.

Georg
  • 5,626
  • 1
  • 23
  • 44
  • thanks for your answer. That explains why I am hitting this problem but does not bring me closer to a solution. I need a "generic" approach to deal with SyncTables for all repositories at the ITableData (or TableData) level (all entities implement this interface/base class) rather than having to operate at the actual entity level (i.e. User). Another possibility is to address IMobileServiceSyncTable by its untyped base class IMobileServiceSyncTable but then I would have to deal with conversion of returned JToken into ITableData/TableData. – Igor Jan 17 '18 at 17:32
  • @Igor I can only repeat what I wrote in the answer: Separate the interface into two separate interfaces, one for reading data, the other one for writing data and you will be able to make them covariant and contravariant, respectively. If that does not work for you, you have to deal with conversion issues. – Georg Jan 18 '18 at 22:35