Complex generics need some weird castings to run
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
|
show 2 more comments
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
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 callnew DummyContainer<SomeOtherContainerType ....>().CanCreate(...)
. It turns around and creates aDefaultContainer
and then tries to pass it to this type'sCanAddRelations
method. But that's expecting aSomeOtherContainerType
, not aDefaultContainer
.
– 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 aDefaultContainer
to aSomeOtherContainerType
)
– Damien_The_Unbeliever
Nov 21 '18 at 10:01
|
show 2 more comments
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
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
c# generics
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 callnew DummyContainer<SomeOtherContainerType ....>().CanCreate(...)
. It turns around and creates aDefaultContainer
and then tries to pass it to this type'sCanAddRelations
method. But that's expecting aSomeOtherContainerType
, not aDefaultContainer
.
– 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 aDefaultContainer
to aSomeOtherContainerType
)
– Damien_The_Unbeliever
Nov 21 '18 at 10:01
|
show 2 more comments
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 callnew DummyContainer<SomeOtherContainerType ....>().CanCreate(...)
. It turns around and creates aDefaultContainer
and then tries to pass it to this type'sCanAddRelations
method. But that's expecting aSomeOtherContainerType
, not aDefaultContainer
.
– 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 aDefaultContainer
to aSomeOtherContainerType
)
– 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
|
show 2 more comments
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
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 aDefaultContainer
and then tries to pass it to this type'sCanAddRelations
method. But that's expecting aSomeOtherContainerType
, not aDefaultContainer
.– 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 aSomeOtherContainerType
)– Damien_The_Unbeliever
Nov 21 '18 at 10:01