14
\$\begingroup\$

As C# doesn't have generic specialisation like C++ template specialisation and I wanted a way to be able to do it, I came up with a little hack that goes a bit like this:

    public sealed class Specialisation<T> 
    { 
        private Specialisation() { }
        private static Specialisation<T> instance = new Specialisation<T>();
        public static Specialisation<T> Instance { get { return instance; } }
    }
    public interface ISpecialisable<T>
    {
        T SpecialisedMethod(Specialisation<T> t);
    }
    public class Specialised : ISpecialisable<int>, ISpecialisable<float>
    {
        public double Value;
        public int SpecialisedMethod(Specialisation<int> t) { return (int)Value; }
        public float SpecialisedMethod(Specialisation<float> t) { return (float)Value; }
    }
    public static class Program
    {
        public static void Main()
        {
           Specialised s = new Specialised();
           s.Value = 12.3456789;
           Console.WriteLine(s.SpecialisedMethod(Specialisation<int>.Instance));
           Console.WriteLine(s.SpecialisedMethod(Specialisation<float>.Instance));
           Console.ReadKey();
        }
    }

Other than this not being the 'right way' to do things and being a complete mistreatment of the method overloading system, are there any possible side effects that could happen as a result of this horrible hack?

EDIT:

An applied example of specialisation in such a manner:

    // assume Specialisation is unchanged
    public interface IConvertible<T> // Not to be confused with System.IConvertible
    {
        T Convert(Specialisation<T> t);
    }
    public class ConvertMe : IConvertible<int>, IConvertible<float>
    {
        public double Value;
        public int Convert(Specialisation<int> t) { return (int)Value; }
        public float Convert(Specialisation<float> t) { return (float)Value; }
    }
    public static class Program
    {
        public static void Main()
        {
           ConvertMe c = new ConvertMe();
           c.Value = 12.3456789;
           Console.WriteLine(c.Convert(Specialisation<int>.Instance));
           Console.WriteLine(c.Convert(Specialisation<float>.Instance));
           Console.ReadKey();
        }
    }

EDIT: Another example I just thought of as an extension of/alternative to the IConvertible<T> example. This technique allows the normal generic method syntax to be used externally, whilst allowing the class developer to internally define specialisations.

public interface IConvertible<T>
{
    T Convert(Specialisation<T> t);
    T Convert<T>();
}
// assume Specialisation<T> is unchanged
public class ConvertMe : IConvertible<int>, IConvertible<float>
{
    public double Value;
    int IConvertible<int>.Convert(Specialisation<int> t) { return (int)Value; }
    float IConvertible<float>.Convert(Specialisation<float> t) { return (float)Value; }
    public T Convert<T>() { return ((IConvertible<T>)this).Convert(Specialisation<T>.Instance); }
}
public static class Program
{
    public static void Main()
    {
        ConvertMe c = new ConvertMe();
        c.Value = 12.3456789;
        Console.WriteLine(c.Convert<int>());
        Console.WriteLine(c.Convert<float>());
        Console.ReadKey();
    }
}

This use of specialisation could be generalised to other applications in order to provide specialised behaviour. I won't add any more examples as the ones provided are long enough as it is, and I'm sure everyone here has the skill and imagination to find other applications of this pattern.

\$\endgroup\$

migrated from stackoverflow.com Feb 16 '14 at 5:14

This question came from our site for professional and enthusiast programmers.

  • 2
    \$\begingroup\$ " are there any possible side effects that could happen as a result of this horrible hack?" Do you mean like unemployment? \$\endgroup\$ – Steve Wellens Feb 16 '14 at 5:11
  • 1
    \$\begingroup\$ I'm not employed anyway. \$\endgroup\$ – Pharap Feb 16 '14 at 5:16
  • 4
    \$\begingroup\$ Could you explain why would you want to do this? How is your code better than s.SpecialisedMethodForInt()? \$\endgroup\$ – svick Feb 16 '14 at 10:37
  • 2
    \$\begingroup\$ I find it pretty "special"... what is it used for? Why the static stuff? \$\endgroup\$ – Mathieu Guindon Feb 16 '14 at 15:05
  • \$\begingroup\$ @svick By doing it this way, ISpecialisable only needs to be declared once and it can be used for any new classes or interfaces, thus it is more extensible than just creating new interfaces each time you come up with a new type to specialise to. \$\endgroup\$ – Pharap Feb 17 '14 at 0:21
6
\$\begingroup\$

First of all, I would question the need for such interface. It makes sense for a type to hold a value, but I think it doesn't make much sense for a type to decide to what types to convert that value, or how.

Second, if you really do need this, then I would prefer each target type to have a separate method, like ConvertToInt() (or ConvertToInt32() if you follow the naming guidelines).

You don't necessarily need an interface for this, but even if you do, you're exchanging a small inconvenience in implementation (having to write few very simple interfaces with some repetition) for a big inconvenience in usage (having to write that Specialisation<int>.Instance every time and making the code less readable). So, I think using this approach is worth it even with interfaces.

Third, if you really do need this and you want to take advantage of generics, you could use explicit interface implementation and casting to decide to what type to convert. Something like:

public interface IConvertible<T>
{
    T Convert();
}

public sealed class ConvertMe : IConvertible<int>, IConvertible<float>
{
    public double Value;
    int IConvertible<int>.Convert() { return (int)Value; }
    float IConvertible<float>.Convert() { return (float)Value; }
}

public static class Program
{
    public static void Main()
    {
       ConvertMe c = new ConvertMe();
       c.Value = 12.3456789;
       Console.WriteLine(((IConvertible<int>)c).Convert());
       Console.WriteLine(((IConvertible<float>)c).Convert());
    }
}

This results in code that is shorter and more readable (I think) than your version.

One issue with it is that if you don't make ConvertMe sealed, trying to convert to an unsupported type becomes a runtime exception, instead of a compile-time error.

\$\endgroup\$
  • \$\begingroup\$ Personally I don't like the ConvertToType naming style because it produces multiple functions with the same purpose that semantically differ only in type (which is what the whole point of specialisation is - being able to define different behaviours dependent on type alone). The casting to an interface variation is definitely useful; personally I find it harder to read though because of the additional brackets it introduces. The point about making ConvertMe sealed is also valid, though it's not too bad considering that this is a hack - you'd expect limitations like that. \$\endgroup\$ – Pharap Feb 17 '14 at 18:13
3
\$\begingroup\$

I don't think that there any nasty hidden downsides to it. As you noted C# doesn't have real specialisations and the only to have the same method just differing by return type is the way you chose - so if that's what you need then that's what you have to do.

Only note is that I'd consider adding a non-generic class like this

public static class Specialisation
{
    public static Specialisation<T> For<T>()
    {
        return Specialisation<T>.Instance;
    }
}

Then you can use it like this:

Specialised s = new Specialised();
s.Value = 12.3456789;
Console.WriteLine(s.SpecialisedMethod(Specialisation.For<int>()));
Console.WriteLine(s.SpecialisedMethod(Specialisation.For<float>()));
Console.ReadKey();

which reads a tiny bit nicer.

You can hide the generic class as internal in your assembly and expose an ISpecialisation interface if you don't want users of your system exposed to the generic and non-generic versions.

\$\endgroup\$
  • \$\begingroup\$ I like the idea of the non-generic class. I'm partly against the ISpecialisation purely because other people can derive from it, which partly defeats the point of offering singletons to use as the generic parameters, but it does offer the user more flexibility. Flexibility to do what exactly I don't know, but flexibility nonetheless. \$\endgroup\$ – Pharap Feb 17 '14 at 17:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.