[{"data":1,"prerenderedAt":625},["ShallowReactive",2],{"blog:2006:substitutabilityofgenerictypesindotnet":3,"blogMore-Development":611,"comments-substitutabilityofgenerictypesindotnet":624},{"id":4,"title":5,"body":6,"category":594,"commentCount":39,"date":595,"description":596,"excerpt":597,"extension":598,"filenames":599,"hidden":600,"image":599,"meta":601,"minutes":80,"navigation":464,"path":602,"seo":603,"showCategory":599,"stem":604,"tags":605,"updated":599,"url":608,"wordCount":609,"__hash__":610},"content\u002Fblog\u002F2006\u002Fsubstitutabilityofgenerictypesindotnet.md","Substitutability of generic types in .NET",{"type":7,"value":8,"toc":588},"minimark",[9,14,18,26,95,108,112,115,200,210,228,231,253,270,274,277,350,353,398,405,409,412,415,508,511,564,575,578,584],[10,11,13],"h2",{"id":12},"the-is-operator","The “is” operator",[15,16,17],"p",{},"Anyone using object orientated programming soon finds themselves wanting to perform some action only if an object is of some type or implements some interface.",[15,19,20,21,25],{},"The easiest way is to do this is to use the ",[22,23,24],"code",{},"is"," operator. Typically it will appear something like:",[27,28,33],"pre",{"className":29,"code":30,"language":31,"meta":32,"style":32},"language-csharp shiki shiki-themes everforest-light dracula","object objA = FunctionToGetAnObject();\nif (obj is SomeClassOrInterface)\n    ((SomeClassOrInterface) obj).SomeMethod();\n","csharp","",[22,34,35,59,78],{"__ignoreMap":32},[36,37,40,44,48,52,56],"span",{"class":38,"line":39},"line",1,[36,41,43],{"class":42},"sXAHl","object",[36,45,47],{"class":46},"s6Vpi"," objA ",[36,49,51],{"class":50},"s9HRq","=",[36,53,55],{"class":54},"sS4Kt"," FunctionToGetAnObject",[36,57,58],{"class":46},"();\n",[36,60,62,66,69,71,75],{"class":38,"line":61},2,[36,63,65],{"class":64},"smiwp","if",[36,67,68],{"class":46}," (obj ",[36,70,24],{"class":64},[36,72,74],{"class":73},"snuxY"," SomeClassOrInterface",[36,76,77],{"class":46},")\n",[36,79,81,84,87,90,93],{"class":38,"line":80},3,[36,82,83],{"class":46},"    ((",[36,85,86],{"class":73},"SomeClassOrInterface",[36,88,89],{"class":46},") obj).",[36,91,92],{"class":54},"SomeMethod",[36,94,58],{"class":46},[15,96,97,98,101,102,104,105,107],{},"Here if ",[22,99,100],{},"obj_A"," is of a type that implements SomeClassOrInterface anywhere in its inheritance tree then ",[22,103,92],{}," of ",[22,106,86],{}," will be called.",[10,109,111],{"id":110},"test-substitution-with-generic-types","Test substitution with generic types",[15,113,114],{},"Now lets say we have our own generic type. Here’s one to illustrate a point;",[27,116,118],{"className":29,"code":117,"language":31,"meta":32,"style":32},"public class DamoClass\u003CT> {\n    public T GetProperty { get { ... } }\n    public T SetProperty { set { ... } }\n    public bool IsValid { return true; }\n}\n",[22,119,120,142,165,183,194],{"__ignoreMap":32},[36,121,122,125,128,132,135,139],{"class":38,"line":39},[36,123,124],{"class":50},"public",[36,126,127],{"class":64}," class",[36,129,131],{"class":130},"sPLAf"," DamoClass",[36,133,134],{"class":46},"\u003C",[36,136,138],{"class":137},"sAO9U","T",[36,140,141],{"class":46},"> {\n",[36,143,144,147,150,153,156,159,162],{"class":38,"line":61},[36,145,146],{"class":50},"    public",[36,148,149],{"class":73}," T",[36,151,152],{"class":46}," GetProperty { ",[36,154,155],{"class":42},"get",[36,157,158],{"class":46}," { ",[36,160,161],{"class":50},"..",[36,163,164],{"class":46},". } }\n",[36,166,167,169,171,174,177,179,181],{"class":38,"line":80},[36,168,146],{"class":50},[36,170,149],{"class":73},[36,172,173],{"class":46}," SetProperty { ",[36,175,176],{"class":42},"set",[36,178,158],{"class":46},[36,180,161],{"class":50},[36,182,164],{"class":46},[36,184,186,188,191],{"class":38,"line":185},4,[36,187,146],{"class":50},[36,189,190],{"class":42}," bool",[36,192,193],{"class":46}," IsValid { return true; }\n",[36,195,197],{"class":38,"line":196},5,[36,198,199],{"class":46},"}\n",[15,201,202,203,205,206,209],{},"If we want to check ",[22,204,100],{}," supports ",[22,207,208],{},"DamoClass\u003CT>"," but we don’t care what type T is you might expect to be able to perform;",[27,211,213],{"className":29,"code":212,"language":31,"meta":32,"style":32},"if (objA is DamoClass)\n",[22,214,215],{"__ignoreMap":32},[36,216,217,219,222,224,226],{"class":38,"line":39},[36,218,65],{"class":64},[36,220,221],{"class":46}," (objA ",[36,223,24],{"class":64},[36,225,131],{"class":73},[36,227,77],{"class":46},[15,229,230],{},"But that results in a compilation error because you can’t reference a generic class without specifying the type parameters regardless of whether you care about them or not. You might then try;",[27,232,234],{"className":29,"code":233,"language":31,"meta":32,"style":32},"if (objA is DamoClass\u003Cobject>)\n",[22,235,236],{"__ignoreMap":32},[36,237,238,240,242,244,246,248,250],{"class":38,"line":39},[36,239,65],{"class":64},[36,241,221],{"class":46},[36,243,24],{"class":64},[36,245,131],{"class":73},[36,247,134],{"class":46},[36,249,43],{"class":42},[36,251,252],{"class":46},">)\n",[15,254,255,256,258,259,262,263,265,266,269],{},"After all every class inherits from ",[22,257,43],{},". This compiles fine but at runtime if ",[22,260,261],{},"objA"," is of any type other than ",[22,264,43],{},", e.g. ",[22,267,268],{},"DamoClass\u003Cstring>"," it will return false.",[10,271,273],{"id":272},"why","Why?",[15,275,276],{},"The following might be what you had in mind;",[27,278,280],{"className":29,"code":279,"language":31,"meta":32,"style":32},"if (objA is DamoClass\u003Cobject>) {\n    ((DamoClass\u003Cobject>) objA).IsValid();\n    object test = ((DamoClass\u003Cobject>) objA).GetProperty;\n}\n",[22,281,282,299,318,346],{"__ignoreMap":32},[36,283,284,286,288,290,292,294,296],{"class":38,"line":39},[36,285,65],{"class":64},[36,287,221],{"class":46},[36,289,24],{"class":64},[36,291,131],{"class":73},[36,293,134],{"class":46},[36,295,43],{"class":42},[36,297,298],{"class":46},">) {\n",[36,300,301,303,306,308,310,313,316],{"class":38,"line":61},[36,302,83],{"class":46},[36,304,305],{"class":73},"DamoClass",[36,307,134],{"class":46},[36,309,43],{"class":42},[36,311,312],{"class":46},">) objA).",[36,314,315],{"class":54},"IsValid",[36,317,58],{"class":46},[36,319,320,323,326,328,331,333,335,337,339,343],{"class":38,"line":80},[36,321,322],{"class":42},"    object",[36,324,325],{"class":46}," test ",[36,327,51],{"class":50},[36,329,330],{"class":46}," ((",[36,332,305],{"class":73},[36,334,134],{"class":46},[36,336,43],{"class":42},[36,338,312],{"class":46},[36,340,342],{"class":341},"sSKRk","GetProperty",[36,344,345],{"class":46},";\n",[36,347,348],{"class":38,"line":185},[36,349,199],{"class":46},[15,351,352],{},"Which looks fine but if .NET allowed that then how would it prevent;",[27,354,356],{"className":29,"code":355,"language":31,"meta":32,"style":32},"if (objA is DamoClass\u003Cobject>)\n    ((DamoClass\u003Cobject>) objA).SetProperty = 11;\n",[22,357,358,374],{"__ignoreMap":32},[36,359,360,362,364,366,368,370,372],{"class":38,"line":39},[36,361,65],{"class":64},[36,363,221],{"class":46},[36,365,24],{"class":64},[36,367,131],{"class":73},[36,369,134],{"class":46},[36,371,43],{"class":42},[36,373,252],{"class":46},[36,375,376,378,380,382,384,386,389,392,396],{"class":38,"line":61},[36,377,83],{"class":46},[36,379,305],{"class":73},[36,381,134],{"class":46},[36,383,43],{"class":42},[36,385,312],{"class":46},[36,387,388],{"class":341},"SetProperty",[36,390,391],{"class":50}," =",[36,393,395],{"class":394},"s3Ipq"," 11",[36,397,345],{"class":46},[15,399,400,401,404],{},"When ",[22,402,403],{},"objA.AssociatedObject"," might well be something other than an integer? It can’t, so it doesn’t.",[10,406,408],{"id":407},"solution","Solution",[15,410,411],{},"The solution is to split your generic class into two classes.",[15,413,414],{},"The first contains your non-generic methods and properties and the second inherits from the first adding all the generic specialisation. The good news is that they can even have the same name.",[27,416,418],{"className":29,"code":417,"language":31,"meta":32,"style":32},"public class DamoClass {\n    public object GetProperty { get { ... } }\n    public bool IsValid { return true; }\n}\n\npublic class DamoClass\u003CT> : DamoClass {\n    public T SetProperty { set { ... } }\n}\n",[22,419,420,431,448,456,460,466,486,503],{"__ignoreMap":32},[36,421,422,424,426,428],{"class":38,"line":39},[36,423,124],{"class":50},[36,425,127],{"class":64},[36,427,131],{"class":130},[36,429,430],{"class":46}," {\n",[36,432,433,435,438,440,442,444,446],{"class":38,"line":61},[36,434,146],{"class":50},[36,436,437],{"class":42}," object",[36,439,152],{"class":46},[36,441,155],{"class":42},[36,443,158],{"class":46},[36,445,161],{"class":50},[36,447,164],{"class":46},[36,449,450,452,454],{"class":38,"line":80},[36,451,146],{"class":50},[36,453,190],{"class":42},[36,455,193],{"class":46},[36,457,458],{"class":38,"line":185},[36,459,199],{"class":46},[36,461,462],{"class":38,"line":196},[36,463,465],{"emptyLinePlaceholder":464},true,"\n",[36,467,469,471,473,475,477,479,482,484],{"class":38,"line":468},6,[36,470,124],{"class":50},[36,472,127],{"class":64},[36,474,131],{"class":130},[36,476,134],{"class":46},[36,478,138],{"class":137},[36,480,481],{"class":46},"> : ",[36,483,305],{"class":73},[36,485,430],{"class":46},[36,487,489,491,493,495,497,499,501],{"class":38,"line":488},7,[36,490,146],{"class":50},[36,492,149],{"class":73},[36,494,173],{"class":46},[36,496,176],{"class":42},[36,498,158],{"class":46},[36,500,161],{"class":50},[36,502,164],{"class":46},[36,504,506],{"class":38,"line":505},8,[36,507,199],{"class":46},[15,509,510],{},"Then you are free to do;",[27,512,514],{"className":29,"code":513,"language":31,"meta":32,"style":32},"if (objA is DamoClass) {\n    ((DamoClass) objA).IsValid();\n    object test = ((DamoClass) objA).GetProperty;\n}\n",[22,515,516,529,542,560],{"__ignoreMap":32},[36,517,518,520,522,524,526],{"class":38,"line":39},[36,519,65],{"class":64},[36,521,221],{"class":46},[36,523,24],{"class":64},[36,525,131],{"class":73},[36,527,528],{"class":46},") {\n",[36,530,531,533,535,538,540],{"class":38,"line":61},[36,532,83],{"class":46},[36,534,305],{"class":73},[36,536,537],{"class":46},") objA).",[36,539,315],{"class":54},[36,541,58],{"class":46},[36,543,544,546,548,550,552,554,556,558],{"class":38,"line":80},[36,545,322],{"class":42},[36,547,325],{"class":46},[36,549,51],{"class":50},[36,551,330],{"class":46},[36,553,305],{"class":73},[36,555,537],{"class":46},[36,557,342],{"class":341},[36,559,345],{"class":46},[36,561,562],{"class":38,"line":185},[36,563,199],{"class":46},[15,565,566,567,570,571,574],{},"In fact, .NET itself uses this technique for ",[22,568,569],{},"IEnumerable"," and ",[22,572,573],{},"IEnumerable\u003CT>",".",[15,576,577],{},"One drawback with this technique is that you can’t redefine the type of a member in the subclass; maybe that’ll make C# 3.0.",[15,579,580],{},[581,582,583],"em",{},"[)amien",[585,586,587],"style",{},"html pre.shiki code .sXAHl, html code.shiki .sXAHl{--shiki-default:#3A94C5;--shiki-dark:#FF79C6}html pre.shiki code .s6Vpi, html code.shiki .s6Vpi{--shiki-default:#5C6A72;--shiki-dark:#F8F8F2}html pre.shiki code .s9HRq, html code.shiki .s9HRq{--shiki-default:#F57D26;--shiki-dark:#FF79C6}html pre.shiki code .sS4Kt, html code.shiki .sS4Kt{--shiki-default:#8DA101;--shiki-dark:#50FA7B}html pre.shiki code .smiwp, html code.shiki .smiwp{--shiki-default:#F85552;--shiki-dark:#FF79C6}html pre.shiki code .snuxY, html code.shiki .snuxY{--shiki-default:#3A94C5;--shiki-default-font-style:inherit;--shiki-dark:#8BE9FD;--shiki-dark-font-style:italic}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sPLAf, html code.shiki .sPLAf{--shiki-default:#3A94C5;--shiki-dark:#8BE9FD}html pre.shiki code .sAO9U, html code.shiki .sAO9U{--shiki-default:#3A94C5;--shiki-default-font-style:inherit;--shiki-dark:#FFB86C;--shiki-dark-font-style:italic}html pre.shiki code .sSKRk, html code.shiki .sSKRk{--shiki-default:#35A77C;--shiki-dark:#F8F8F2}html pre.shiki code .s3Ipq, html code.shiki .s3Ipq{--shiki-default:#DF69BA;--shiki-dark:#BD93F9}",{"title":32,"searchDepth":61,"depth":61,"links":589},[590,591,592,593],{"id":12,"depth":61,"text":13},{"id":110,"depth":61,"text":111},{"id":272,"depth":61,"text":273},{"id":407,"depth":61,"text":408},"Development","2006-05-03T00:59:00+00:00","Why `obj is DamoClass\u003Cobject>` doesn't behave the way you'd expect with .NET generics, and the recommended pattern of splitting a generic class into a non-generic base plus a generic subclass — the same trick .NET uses for IEnumerable.","[object Object]","md",null,false,{},"\u002Fblog\u002F2006\u002Fsubstitutabilityofgenerictypesindotnet",{"title":5,"description":596},"blog\u002F2006\u002Fsubstitutabilityofgenerictypesindotnet",[606,607],".NET","C#","\u002Fblog\u002F2006\u002Fsubstitutabilityofgenerictypesindotnet\u002F",566,"1OmI4Qg5w-09xjtofjTdTWGV2eSbvWa_6JzY8O2lBe4",[612,616,620],{"title":613,"date":614,"url":615},"Transactions in the MongoDB EF Core Provider","2025-10-25","\u002Fblog\u002F2025\u002Fmongodb-explicit-transactions\u002F",{"title":617,"date":618,"url":619},"Queryable Encryption with the MongoDB EF Core Provider","2025-09-22","\u002Fblog\u002F2025\u002Fmongodb-queryable-encryption\u002F",{"title":621,"date":622,"url":623},"Lazy Loading with EF Core Proxies","2025-04-02","\u002Fblog\u002F2025\u002Fef-proxies\u002F",[],1780900533499]