Skip to content

Instantly share code, notes, and snippets.

@PathogenDavid
Created May 31, 2014 11:56
Show Gist options
  • Save PathogenDavid/bdd313ad49d1990059d2 to your computer and use it in GitHub Desktop.
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.
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