Created
May 31, 2014 11:56
-
-
Save PathogenDavid/bdd313ad49d1990059d2 to your computer and use it in GitHub Desktop.
A small playground showing that extension methods do not win over generics like they would if they were part of the extended class.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace ObservedSerialization | |
{ | |
/// <summary> | |
/// Like SerializerMode | |
/// </summary> | |
enum ThingMode | |
{ | |
SwapValues, | |
PrintConsole | |
} | |
/// <summary> | |
/// Like IDataSerializable | |
/// </summary> | |
interface IDoesAThing | |
{ | |
void DoThing(ThingMode mode); | |
} | |
/// <summary> | |
/// Like SharpDX.Mathematics.Interop.Color4 | |
/// </summary> | |
struct Color1 | |
{ | |
public float R; | |
public float G; | |
public float B; | |
public float A; | |
public Color1(float r, float g, float b, float a) | |
{ | |
R = r; | |
G = g; | |
B = b; | |
A = a; | |
} | |
} | |
/// <summary> | |
/// Like the older implementation of Color4 that requires serialization. | |
/// </summary> | |
struct Color2 : IDoesAThing | |
{ | |
public float R; | |
public float G; | |
public float B; | |
public float A; | |
public Color2(float r, float g, float b, float a) | |
{ | |
R = r; | |
G = g; | |
B = b; | |
A = a; | |
} | |
public void DoThing(ThingMode mode) | |
{ | |
if (mode == ThingMode.PrintConsole) | |
{ | |
Console.WriteLine("Color2:[{0},{1},{2},{3}]", R, G, B, A); | |
} | |
else | |
{ | |
float temp1 = R; | |
float temp2 = G; | |
R = A; | |
G = B; | |
B = temp2; | |
A = temp1; | |
} | |
} | |
} | |
/// <summary> | |
/// "Doing things" on this struct is implemented similar to BinarySerializer.Serialize(ref int value) | |
/// </summary> | |
struct Color3 | |
{ | |
public float R; | |
public float G; | |
public float B; | |
public float A; | |
public Color3(float r, float g, float b, float a) | |
{ | |
R = r; | |
G = g; | |
B = b; | |
A = a; | |
} | |
} | |
/// <summary> | |
/// Like BinarySerializer | |
/// </summary> | |
class ThingDoer | |
{ | |
public void DoThing<T>(ref T val, ThingMode mode) where T : IDoesAThing | |
{ | |
val.DoThing(mode); | |
} | |
public void DoThing(ref Color3 val, ThingMode mode) | |
{ | |
if (mode == ThingMode.PrintConsole) | |
{ | |
Console.WriteLine("Color3:[{0},{1},{2},{3}]", val.R, val.G, val.B, val.A); | |
} | |
else | |
{ | |
float temp1 = val.R; | |
float temp2 = val.G; | |
val.R = val.A; | |
val.G = val.B; | |
val.B = temp2; | |
val.A = temp1; | |
} | |
} | |
} | |
/// <summary> | |
/// This implements doing things on Color the same way we do things on Color3, but as an extension method. | |
/// </summary> | |
static class ThingDoerEx | |
{ | |
public static void DoThing(this ThingDoer derp, ref Color1 val, ThingMode mode) | |
{ | |
if (mode == ThingMode.PrintConsole) | |
{ | |
Console.WriteLine("Color1:[{0},{1},{2},{3}]", val.R, val.G, val.B, val.A); | |
} | |
else | |
{ | |
float temp1 = val.R; | |
float temp2 = val.G; | |
val.R = val.A; | |
val.G = val.B; | |
val.B = temp2; | |
val.A = temp1; | |
} | |
} | |
} | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
Color1 c1 = new Color1(1f, 2f, 3f, 4f); | |
Color2 c2 = new Color2(5f, 6f, 7f, 8f); | |
Color3 c3 = new Color3(9f, 0f, 1f, 2f); | |
ThingDoer doer = new ThingDoer(); | |
//==For the lines labeled with error CS0315== | |
//C# tries to use the generic, which fails. | |
//It doesn't see that the extension method in ThingDoerEx as a candidate as long as it | |
//has the same name as the generic method even though it would consider it if it were | |
//moved directly into the ThingDoer class. | |
doer.DoThing(ref c1, ThingMode.PrintConsole);//CS0315 | |
doer.DoThing(ref c2, ThingMode.PrintConsole); | |
doer.DoThing(ref c3, ThingMode.PrintConsole); | |
doer.DoThing(ref c1, ThingMode.SwapValues);//CS0315 | |
doer.DoThing(ref c2, ThingMode.SwapValues); | |
doer.DoThing(ref c3, ThingMode.SwapValues); | |
doer.DoThing(ref c1, ThingMode.PrintConsole);//CS0315 | |
doer.DoThing(ref c2, ThingMode.PrintConsole); | |
doer.DoThing(ref c3, ThingMode.PrintConsole); | |
Console.ReadLine(); | |
/* | |
Expected output: | |
Color1:[1,2,3,4] | |
Color2:[5,6,7,8] | |
Color3:[9,0,1,2] | |
Color1:[4,3,2,1] | |
Color2:[8,7,6,5] | |
Color3:[2,1,0,9] | |
*/ | |
} | |
} | |
} | |
/* Unfortunately this is a symptom of how the appropriate function is chosen in C#. | |
* In the C# standard §7.6.5.1: when a generic is chosen as a candidate for the method to be called, | |
* there is no check made on the generic constraints. This means it is considered a valid candidate | |
* until later in the same section where generic constraints are checked. Generic constraints failing | |
* is a compile-timer error right then and there. So the compiler never reaches the stages described in | |
* §7.6.5.1 for extension method evaluation. | |
* | |
* This situation unfortunately is simply not considered by the method binding algorithm. | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment