0

As documented upstream, one cannot use 'array' as base constraints:

[...]Some types are disallowed as a base class constraint: Object, Array, and ValueType.[...]

However there seems to be minimal type inference for array as seen at:

My goal is to extend the original SO post, to handle also array types:

So my naive attempt is as follow:

public class JsonDictionary
{
    public static readonly Key<int> Int = new Key<int> { Name = "INT" };
    public static readonly Key<int[]> Int3 = new Key<int[]> { Name = "INT3" };
    
    IDictionary<string, object> _data;
    public JsonDictionary()
    {
        _data = new Dictionary<string, object>();
    }
    public void Set<T>(Key<T> key, T obj)
    {
        if (obj is int[] objArray) // FIXME: runtime check
        {
            if (objArray.Length != 3)
                throw new FormatException("Illegal INT3");
        }
        _data[key.Name] = obj;
    }
    public T Get<T>(Key<T> key)
    {
        return (T)_data[key.Name];
    }
    public class Key<T>
    {
        public string Name { get; init; }
    }
}

Usage is simply:

var d = new JsonDictionary();
d.Set(JsonDictionary.Int, 42);
var i = d.Get(JsonDictionary.Int);
d.Set(JsonDictionary.Int3, new int[] { 1, 2, 3 });
var i3 = d.Get(JsonDictionary.Int3);
Assert.Throws<FormatException>(() => d.Set(JsonDictionary.Int3, new int[] { 1, 2 }));

Notice how I used a runtime check (obj is int[] objArray) because I could not use some kind of constraint for 'array'.

Is there a way to rewrite the above so that I can have two different Set functions, one for T and one for T[]. Otherwise I would need to handle all possible types with an ugly if/else:

        if (obj is int[] objArray)
        {
            if (objArray.Length != 3)
                throw new FormatException("Illegal INT3");
        }
        else if (obj is ushort[] objArray)
        {
            if (objArray.Length != 3)
                throw new FormatException("Illegal USHORT3");
        }
        else if (obj is double[] objArray)
        {
            if (objArray.Length != 3)
                throw new FormatException("Illegal DOUBLE3");
        }
        [...]
ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
malat
  • 12,152
  • 13
  • 89
  • 158
  • 2
    For clarification: are you asking how to create a generic method with a constraint on the input being an array, or are you asking how to determine if a variable holds an array? – ProgrammingLlama Sep 08 '21 at 06:18
  • Consider alternative approach where you create custom type to represent your constraints. _Make illegal represenation impossible_ – Fabio Sep 08 '21 at 06:21
  • @Llama: IMHO the latter: "... because I could not use some kind of constraint for 'array'." I think your deleted answer is correct – Thomas Weller Sep 08 '21 at 06:23
  • what about `where T: IEnumerable`? – MakePeaceGreatAgain Sep 08 '21 at 06:24
  • @HimBromBeere Being careful with `string` (or should I say `IEnumerable`) of course :) – ProgrammingLlama Sep 08 '21 at 06:25
  • 1
    @Llama I agree, however as OP is asking for a **generic** function, so `string` should be valid as well. Otherweise he shouldn't use generics in the first place. – MakePeaceGreatAgain Sep 08 '21 at 06:25
  • @HimBromBeere I know, but I just wanted to point it out in case OP decides to go that route and gets surprised by `string` being treated as an `IEnumerable`. – ProgrammingLlama Sep 08 '21 at 06:26
  • @HimBromBeere based on `d.Set(JsonDictionary.Int, 42);` it seems that not only arrays are allowed. – Guru Stron Sep 08 '21 at 06:27
  • 3
    "Is there a way to rewrite the above so that I can have two different `Set` functions, one for `T` and one for `T[]`" I'm pretty sure overload resolution will pick the array overload if you pass an array type, so you should be able to just write another overload like `public void Set(Key key, T[] obj)`. ([this is also the answer to one of your linked posts](https://stackoverflow.com/a/14795031/5133585)) What's preventing you from doing that? – Sweeper Sep 08 '21 at 06:27
  • 1
    please add some information about which types the constraint should actually match. What about `string[]` for example? Would `List` be okay also? Or `DateTime[]`? Ask youself what those types have in common. – MakePeaceGreatAgain Sep 08 '21 at 06:40

2 Answers2

3

You can check if it's an Array:

if (obj is Array arr && arr.Length != 3)
{
    throw new FormatException("Illegal array. Expected length 3.");
}
ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
2

It seems like you shouldn't use generics here at all. There's absolutely nothing common between an int[] and a double[], as there is no constraint for numbers - nor for arrays.

You could in some way workaround your problem with a constraint for IEnumerable, but that would also fit int[], but also a List<MyClass>.

So even if there was a constraint for arrays, there's no constraint for the array-type.

So after all you should have different method for different types:

Set(int i) => ...
Set(int[] arr) => ...
Set(double d) => ...
Set(double[]) => ...
MakePeaceGreatAgain
  • 35,491
  • 6
  • 60
  • 111
  • 1
    Having a different method seems like the better option overall, assuming OP won't ever pass an `object` and expect the method to "just handle it". – ProgrammingLlama Sep 08 '21 at 06:40
  • I fail to understand the first sentence `It seems like you shouldn't use generics here at all.` Can you include a minimal code sample on how you would have done the above ? – malat Sep 08 '21 at 06:45
  • @malat just one method per type: one for `int`, one for `int[]`, one for `double`, one for `double[]` and so forth. – MakePeaceGreatAgain Sep 08 '21 at 06:48
  • OK, thanks for the clarification now I understand. Anyway the above is based on previous post: https://stackoverflow.com/a/2178769/136285. I still believe that your `Set` are kindda duplicate and I assumed Generic would be a good fit. – malat Sep 08 '21 at 06:53
  • 1
    @malat to reduce the duplication, you may have a private method that does all the common logic. – MakePeaceGreatAgain Sep 08 '21 at 06:56