Complex generics need some weird castings to run












0














Playing around with genericity, I built a simple social network model, base on persons and relations between them.



All of those are stored in a container.



For each container, I want to be able to add some validation to check some specific constraints.



Here is the maybe overly complex model, but as said, I'm playing with genericity, so it's kind of on purpose:



public interface IContainer<TPerson, TRelation>
where TRelation : IRelation<TPerson>
{
IReadOnlyCollection<TPerson> Persons { get; }
IReadOnlyCollection<TRelation> Relations { get; }
}

public class DefaultContainer<TPerson, TRelation>
: IContainer<TPerson, TRelation>
where TRelation : IRelation<TPerson>
{
private readonly HashSet<TPerson> _persons;
private readonly List<TRelation> _relations;

//...
}

public class DummyValidator<TContainer, TPerson, TRelation>
: IValidator<TContainer, TPerson, TRelation>
where TContainer : IMutableContainer<TPerson, TRelation>
where TRelation : IRelation<TPerson>
{
public bool CanAddRelations(TContainer container, IEnumerable<TRelation> relations)
{
// guard clauses

return true; // whatever
}

public bool CanCreate(IEnumerable<TPerson> persons, IEnumerable<TRelation> relations)
{
// guard clauses

return CanAddRelations(new DefaultContainer<TPerson, TRelation>(), relations);
}

//...
}

[Fact]
public void Test()
{
var sut = new DummyValidator<IContainer<int, IRelation<int>>, int, IRelation<int>>();
sut.CanCreate(Enumerable.Empty<int>(), Enumerable.Empty<IRelation<int>>());
}


Take a look at the CanCreate method of the DummyValidator class.
It creates a new container (implementing TContainer):



return CanAddRelations(new DefaultContainer<TPerson, TRelation>(), relations);


So everything should be fine, as far as I can tell. But the compiler complains, saying:




cannot convert from 'Alcuin.Graph.Containers.DefaultContainer' to 'TContainer'




My question is: why does it complain about that, even if every generic parameter is described and always equivalent ?



So I tried some explicit casts to investigate:



return CanAddRelations((TContainer)new DefaultContainer<TPerson, TRelation>(), relations);


and



return CanAddRelations((IMutableContainer<TPerson, TRelation>)new DefaultContainer<TPerson, TRelation>(), relations);


both fail, but:



return CanAddRelations((TContainer)(IMutableContainer<TPerson, TRelation>)new DefaultContainer<TPerson, TRelation>(), relations);


compiles and runs correctly...



Can anyone shed some light on what is happening here ?
Am I missing something obvious ? Am I reaching the limits of C#'s genericity engine ?










share|improve this question




















  • 2




    Would it be possible for you to create a smaller MCVE? It seems that a substantial part of your question could be dropped from your question, do you agree?
    – Micha Wiedenmann
    Nov 21 '18 at 9:24












  • @MichaWiedenmann I agree that it should be possible, I'm looking right now at what can be removed while preserving the behavior.
    – xlecoustillier
    Nov 21 '18 at 9:33










  • @MichaWiedenmann I removed a lot of stuff, is it better now ? I don't know if I can remove some more...
    – xlecoustillier
    Nov 21 '18 at 9:47








  • 1




    So I call new DummyContainer<SomeOtherContainerType ....>().CanCreate(...). It turns around and creates a DefaultContainer and then tries to pass it to this type's CanAddRelations method. But that's expecting a SomeOtherContainerType, not a DefaultContainer.
    – Damien_The_Unbeliever
    Nov 21 '18 at 9:58












  • And once you're doing multiple casts, you're forcing the compiler to give up some of the type information that it has that is currently protecting you and it from a runtime error instead. (E.g. in my previous example, it would blow up at runtime saying it cannot cast a DefaultContainer to a SomeOtherContainerType)
    – Damien_The_Unbeliever
    Nov 21 '18 at 10:01


















0














Playing around with genericity, I built a simple social network model, base on persons and relations between them.



All of those are stored in a container.



For each container, I want to be able to add some validation to check some specific constraints.



Here is the maybe overly complex model, but as said, I'm playing with genericity, so it's kind of on purpose:



public interface IContainer<TPerson, TRelation>
where TRelation : IRelation<TPerson>
{
IReadOnlyCollection<TPerson> Persons { get; }
IReadOnlyCollection<TRelation> Relations { get; }
}

public class DefaultContainer<TPerson, TRelation>
: IContainer<TPerson, TRelation>
where TRelation : IRelation<TPerson>
{
private readonly HashSet<TPerson> _persons;
private readonly List<TRelation> _relations;

//...
}

public class DummyValidator<TContainer, TPerson, TRelation>
: IValidator<TContainer, TPerson, TRelation>
where TContainer : IMutableContainer<TPerson, TRelation>
where TRelation : IRelation<TPerson>
{
public bool CanAddRelations(TContainer container, IEnumerable<TRelation> relations)
{
// guard clauses

return true; // whatever
}

public bool CanCreate(IEnumerable<TPerson> persons, IEnumerable<TRelation> relations)
{
// guard clauses

return CanAddRelations(new DefaultContainer<TPerson, TRelation>(), relations);
}

//...
}

[Fact]
public void Test()
{
var sut = new DummyValidator<IContainer<int, IRelation<int>>, int, IRelation<int>>();
sut.CanCreate(Enumerable.Empty<int>(), Enumerable.Empty<IRelation<int>>());
}


Take a look at the CanCreate method of the DummyValidator class.
It creates a new container (implementing TContainer):



return CanAddRelations(new DefaultContainer<TPerson, TRelation>(), relations);


So everything should be fine, as far as I can tell. But the compiler complains, saying:




cannot convert from 'Alcuin.Graph.Containers.DefaultContainer' to 'TContainer'




My question is: why does it complain about that, even if every generic parameter is described and always equivalent ?



So I tried some explicit casts to investigate:



return CanAddRelations((TContainer)new DefaultContainer<TPerson, TRelation>(), relations);


and



return CanAddRelations((IMutableContainer<TPerson, TRelation>)new DefaultContainer<TPerson, TRelation>(), relations);


both fail, but:



return CanAddRelations((TContainer)(IMutableContainer<TPerson, TRelation>)new DefaultContainer<TPerson, TRelation>(), relations);


compiles and runs correctly...



Can anyone shed some light on what is happening here ?
Am I missing something obvious ? Am I reaching the limits of C#'s genericity engine ?










share|improve this question




















  • 2




    Would it be possible for you to create a smaller MCVE? It seems that a substantial part of your question could be dropped from your question, do you agree?
    – Micha Wiedenmann
    Nov 21 '18 at 9:24












  • @MichaWiedenmann I agree that it should be possible, I'm looking right now at what can be removed while preserving the behavior.
    – xlecoustillier
    Nov 21 '18 at 9:33










  • @MichaWiedenmann I removed a lot of stuff, is it better now ? I don't know if I can remove some more...
    – xlecoustillier
    Nov 21 '18 at 9:47








  • 1




    So I call new DummyContainer<SomeOtherContainerType ....>().CanCreate(...). It turns around and creates a DefaultContainer and then tries to pass it to this type's CanAddRelations method. But that's expecting a SomeOtherContainerType, not a DefaultContainer.
    – Damien_The_Unbeliever
    Nov 21 '18 at 9:58












  • And once you're doing multiple casts, you're forcing the compiler to give up some of the type information that it has that is currently protecting you and it from a runtime error instead. (E.g. in my previous example, it would blow up at runtime saying it cannot cast a DefaultContainer to a SomeOtherContainerType)
    – Damien_The_Unbeliever
    Nov 21 '18 at 10:01
















0












0








0


1





Playing around with genericity, I built a simple social network model, base on persons and relations between them.



All of those are stored in a container.



For each container, I want to be able to add some validation to check some specific constraints.



Here is the maybe overly complex model, but as said, I'm playing with genericity, so it's kind of on purpose:



public interface IContainer<TPerson, TRelation>
where TRelation : IRelation<TPerson>
{
IReadOnlyCollection<TPerson> Persons { get; }
IReadOnlyCollection<TRelation> Relations { get; }
}

public class DefaultContainer<TPerson, TRelation>
: IContainer<TPerson, TRelation>
where TRelation : IRelation<TPerson>
{
private readonly HashSet<TPerson> _persons;
private readonly List<TRelation> _relations;

//...
}

public class DummyValidator<TContainer, TPerson, TRelation>
: IValidator<TContainer, TPerson, TRelation>
where TContainer : IMutableContainer<TPerson, TRelation>
where TRelation : IRelation<TPerson>
{
public bool CanAddRelations(TContainer container, IEnumerable<TRelation> relations)
{
// guard clauses

return true; // whatever
}

public bool CanCreate(IEnumerable<TPerson> persons, IEnumerable<TRelation> relations)
{
// guard clauses

return CanAddRelations(new DefaultContainer<TPerson, TRelation>(), relations);
}

//...
}

[Fact]
public void Test()
{
var sut = new DummyValidator<IContainer<int, IRelation<int>>, int, IRelation<int>>();
sut.CanCreate(Enumerable.Empty<int>(), Enumerable.Empty<IRelation<int>>());
}


Take a look at the CanCreate method of the DummyValidator class.
It creates a new container (implementing TContainer):



return CanAddRelations(new DefaultContainer<TPerson, TRelation>(), relations);


So everything should be fine, as far as I can tell. But the compiler complains, saying:




cannot convert from 'Alcuin.Graph.Containers.DefaultContainer' to 'TContainer'




My question is: why does it complain about that, even if every generic parameter is described and always equivalent ?



So I tried some explicit casts to investigate:



return CanAddRelations((TContainer)new DefaultContainer<TPerson, TRelation>(), relations);


and



return CanAddRelations((IMutableContainer<TPerson, TRelation>)new DefaultContainer<TPerson, TRelation>(), relations);


both fail, but:



return CanAddRelations((TContainer)(IMutableContainer<TPerson, TRelation>)new DefaultContainer<TPerson, TRelation>(), relations);


compiles and runs correctly...



Can anyone shed some light on what is happening here ?
Am I missing something obvious ? Am I reaching the limits of C#'s genericity engine ?










share|improve this question















Playing around with genericity, I built a simple social network model, base on persons and relations between them.



All of those are stored in a container.



For each container, I want to be able to add some validation to check some specific constraints.



Here is the maybe overly complex model, but as said, I'm playing with genericity, so it's kind of on purpose:



public interface IContainer<TPerson, TRelation>
where TRelation : IRelation<TPerson>
{
IReadOnlyCollection<TPerson> Persons { get; }
IReadOnlyCollection<TRelation> Relations { get; }
}

public class DefaultContainer<TPerson, TRelation>
: IContainer<TPerson, TRelation>
where TRelation : IRelation<TPerson>
{
private readonly HashSet<TPerson> _persons;
private readonly List<TRelation> _relations;

//...
}

public class DummyValidator<TContainer, TPerson, TRelation>
: IValidator<TContainer, TPerson, TRelation>
where TContainer : IMutableContainer<TPerson, TRelation>
where TRelation : IRelation<TPerson>
{
public bool CanAddRelations(TContainer container, IEnumerable<TRelation> relations)
{
// guard clauses

return true; // whatever
}

public bool CanCreate(IEnumerable<TPerson> persons, IEnumerable<TRelation> relations)
{
// guard clauses

return CanAddRelations(new DefaultContainer<TPerson, TRelation>(), relations);
}

//...
}

[Fact]
public void Test()
{
var sut = new DummyValidator<IContainer<int, IRelation<int>>, int, IRelation<int>>();
sut.CanCreate(Enumerable.Empty<int>(), Enumerable.Empty<IRelation<int>>());
}


Take a look at the CanCreate method of the DummyValidator class.
It creates a new container (implementing TContainer):



return CanAddRelations(new DefaultContainer<TPerson, TRelation>(), relations);


So everything should be fine, as far as I can tell. But the compiler complains, saying:




cannot convert from 'Alcuin.Graph.Containers.DefaultContainer' to 'TContainer'




My question is: why does it complain about that, even if every generic parameter is described and always equivalent ?



So I tried some explicit casts to investigate:



return CanAddRelations((TContainer)new DefaultContainer<TPerson, TRelation>(), relations);


and



return CanAddRelations((IMutableContainer<TPerson, TRelation>)new DefaultContainer<TPerson, TRelation>(), relations);


both fail, but:



return CanAddRelations((TContainer)(IMutableContainer<TPerson, TRelation>)new DefaultContainer<TPerson, TRelation>(), relations);


compiles and runs correctly...



Can anyone shed some light on what is happening here ?
Am I missing something obvious ? Am I reaching the limits of C#'s genericity engine ?







c# generics






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 21 '18 at 23:54







xlecoustillier

















asked Nov 21 '18 at 9:21









xlecoustillierxlecoustillier

13.8k124772




13.8k124772








  • 2




    Would it be possible for you to create a smaller MCVE? It seems that a substantial part of your question could be dropped from your question, do you agree?
    – Micha Wiedenmann
    Nov 21 '18 at 9:24












  • @MichaWiedenmann I agree that it should be possible, I'm looking right now at what can be removed while preserving the behavior.
    – xlecoustillier
    Nov 21 '18 at 9:33










  • @MichaWiedenmann I removed a lot of stuff, is it better now ? I don't know if I can remove some more...
    – xlecoustillier
    Nov 21 '18 at 9:47








  • 1




    So I call new DummyContainer<SomeOtherContainerType ....>().CanCreate(...). It turns around and creates a DefaultContainer and then tries to pass it to this type's CanAddRelations method. But that's expecting a SomeOtherContainerType, not a DefaultContainer.
    – Damien_The_Unbeliever
    Nov 21 '18 at 9:58












  • And once you're doing multiple casts, you're forcing the compiler to give up some of the type information that it has that is currently protecting you and it from a runtime error instead. (E.g. in my previous example, it would blow up at runtime saying it cannot cast a DefaultContainer to a SomeOtherContainerType)
    – Damien_The_Unbeliever
    Nov 21 '18 at 10:01
















  • 2




    Would it be possible for you to create a smaller MCVE? It seems that a substantial part of your question could be dropped from your question, do you agree?
    – Micha Wiedenmann
    Nov 21 '18 at 9:24












  • @MichaWiedenmann I agree that it should be possible, I'm looking right now at what can be removed while preserving the behavior.
    – xlecoustillier
    Nov 21 '18 at 9:33










  • @MichaWiedenmann I removed a lot of stuff, is it better now ? I don't know if I can remove some more...
    – xlecoustillier
    Nov 21 '18 at 9:47








  • 1




    So I call new DummyContainer<SomeOtherContainerType ....>().CanCreate(...). It turns around and creates a DefaultContainer and then tries to pass it to this type's CanAddRelations method. But that's expecting a SomeOtherContainerType, not a DefaultContainer.
    – Damien_The_Unbeliever
    Nov 21 '18 at 9:58












  • And once you're doing multiple casts, you're forcing the compiler to give up some of the type information that it has that is currently protecting you and it from a runtime error instead. (E.g. in my previous example, it would blow up at runtime saying it cannot cast a DefaultContainer to a SomeOtherContainerType)
    – Damien_The_Unbeliever
    Nov 21 '18 at 10:01










2




2




Would it be possible for you to create a smaller MCVE? It seems that a substantial part of your question could be dropped from your question, do you agree?
– Micha Wiedenmann
Nov 21 '18 at 9:24






Would it be possible for you to create a smaller MCVE? It seems that a substantial part of your question could be dropped from your question, do you agree?
– Micha Wiedenmann
Nov 21 '18 at 9:24














@MichaWiedenmann I agree that it should be possible, I'm looking right now at what can be removed while preserving the behavior.
– xlecoustillier
Nov 21 '18 at 9:33




@MichaWiedenmann I agree that it should be possible, I'm looking right now at what can be removed while preserving the behavior.
– xlecoustillier
Nov 21 '18 at 9:33












@MichaWiedenmann I removed a lot of stuff, is it better now ? I don't know if I can remove some more...
– xlecoustillier
Nov 21 '18 at 9:47






@MichaWiedenmann I removed a lot of stuff, is it better now ? I don't know if I can remove some more...
– xlecoustillier
Nov 21 '18 at 9:47






1




1




So I call new DummyContainer<SomeOtherContainerType ....>().CanCreate(...). It turns around and creates a DefaultContainer and then tries to pass it to this type's CanAddRelations method. But that's expecting a SomeOtherContainerType, not a DefaultContainer.
– Damien_The_Unbeliever
Nov 21 '18 at 9:58






So I call new DummyContainer<SomeOtherContainerType ....>().CanCreate(...). It turns around and creates a DefaultContainer and then tries to pass it to this type's CanAddRelations method. But that's expecting a SomeOtherContainerType, not a DefaultContainer.
– Damien_The_Unbeliever
Nov 21 '18 at 9:58














And once you're doing multiple casts, you're forcing the compiler to give up some of the type information that it has that is currently protecting you and it from a runtime error instead. (E.g. in my previous example, it would blow up at runtime saying it cannot cast a DefaultContainer to a SomeOtherContainerType)
– Damien_The_Unbeliever
Nov 21 '18 at 10:01






And once you're doing multiple casts, you're forcing the compiler to give up some of the type information that it has that is currently protecting you and it from a runtime error instead. (E.g. in my previous example, it would blow up at runtime saying it cannot cast a DefaultContainer to a SomeOtherContainerType)
– Damien_The_Unbeliever
Nov 21 '18 at 10:01














0






active

oldest

votes











Your Answer






StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53408773%2fcomplex-generics-need-some-weird-castings-to-run%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























0






active

oldest

votes








0






active

oldest

votes









active

oldest

votes






active

oldest

votes
















draft saved

draft discarded




















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53408773%2fcomplex-generics-need-some-weird-castings-to-run%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Create new schema in PostgreSQL using DBeaver

Deepest pit of an array with Javascript: test on Codility

Costa Masnaga