Home > Software design >  How do Generics handle constructors
How do Generics handle constructors

Time:12-30

I have a generic class with the following constructors 1. Map(int resolution), 2. Map(int resolution, T defaultValue), 3. Map(int width, int height) and 4. Map(int width, int height, T defaultValue), what happens if one of the inheriting classes requires map<int>, constructors 2 and 3 each have 2 parameters and in the map<int> case they would both be integers.

public abstract class Map<T> {
    private T[,] m_Map;
    private T m_DefaultValue;

    public T this[int x, int y] {
        get {
            return InRange(x,y)?m_Map[x,y]:DefaultValue;
        }
        set {
            if(InRange(x,y)) { // Included to handle confusion between retrieving a non-existent position, and setting one.
                m_Map[x,y]  value;
            }
        }
    }

    public int Width{get { return m_Map.GetLength(0); } }
    public int Height{get { return m_Map.GetLength(1); } }
    public virtual T DefaultValue { get { return m_DefaultValue; } set{ m_DefaultValue = value; } }

    public Map(int resolution) : this(resolution, resolution, default) {}
    public Map(int resolution, T defaultValue) : this(resolution, resolution, defaultValue) {}
    public Map(int width, int height) : this(width, height, default) {}
    public Map(int width, int height, T defaultValue) {
        m_Map = new T[width, height];
        m_DefaultValue = defaultValue;
    }

    public bool InRange(int x, int y) {
        return x>=0&&x<m_Map.GetLength(0)&&y>=0&&y<m_Map.GetLength(1);
    }
}

CodePudding user response:

You can use named arguments to disambiguate the methods.

//Call Map(int,int)
var x = new Map<int>(width: 1, height: 2);

//Call Map(int, T)
var x = new Map<int>(width: 1, defaultValue: 3);

The same will work with the base keyword.

class MyClass : Map<int>
{
    public MyClass(int width, int defaultValue) : base( width: width, defaultValue: defaultValue)
    {
    }
}

CodePudding user response:

This is technically possible in the CLI. The ECMA-335 specification specifically points this out:

II.9.8 Signatures and binding

It is possible for distinct members to have identical types when instantiated, but which can be distinguished by MemberRef.
[Example:

.class public C`2<S,T> {
.field string f
.field !0 f
.method instance void m(!0 x) {...}
.method instance void m(!1 x) {...}
.method instance void m(string x) {...}
}

The closed type C``2<string,string> is valid: it has three methods called m, all with the same parameter type; and two fields called f with the same type. They are all distinguished through the MemberRef encoding described above:

string C`2<string, string>::f
!0 C<string, string>::f
void C`2<string, string>::m(!0)
void C`2<string, string>::m(!1)
void C`2<string, string>::m(string)

The way in which a source language might resolve this kind of overloading is left to each individual language. For example, many might disallow such overloads. end example]

In the case of C#, this is not considered illegal and will use standard method overload resolution:

  1. Otherwise, given any two members of the set, M and N, apply the following tie-breaking rules, in order:
    ..snip..
    bv. Before type arguments have been substituted, if M is less generic (Section Genericity) than N, eliminate N from the set.

So the ones which are non-generic are taken in favour of the ones which are generic.

Note that the overload resolution only takes into account information available at compile-time (dynamic excepted). So the constructor

public Map(int resolution, T defaultValue) : this(resolution, resolution, defaultValue) {}

will always call

public Map(int width, int height, T defaultValue) {

and never call this one even for a Map<int>

public Map(int width, int height, int defaultValue) {
  • Related