1

I'd like to do a function like below. K and L are generic type parameters. (it's a customization of MultiKeyDictionary by Aron Weiler in case you wonder)

protected void Dissociate(K primaryKey, L subKey)
{
    primaryToSubkeyMapping.Remove(primaryKey??subDictionary[subKey]);
    subDictionary.Remove(subKey??primaryToSubkeyMapping[primaryKey]);
}

Each argument is optional but at least one needs to be present; null means the absence. The problem is, if the generic parameter is a value type, I need to wrap the function's argument with Nullable so primaryKey?? is valid and so I can pass null to specify the argument's absence. But, Nullable<K> is invalid too if K is a reference type instead!

So, can I somehow write the implementation so it's valid for both cases?

Community
  • 1
  • 1
ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152
  • Pay attention to the fact I do need to wrap a value type into `Nullable`. I've edited the question to emphasize this. – ivan_pozdeev Nov 08 '14 at 10:48
  • 1
    you want to make them nullable and also pass reference types at the same time? if so it is not possible, in order to make them `Nullable` you have to add a `where T : struct` constraint. – Selman Genç Nov 08 '14 at 10:50
  • So, can I make two method implementations so an appropriate one is used depending on whether the constraint holds? – ivan_pozdeev Nov 08 '14 at 10:58
  • you could try using method overloading but then you will need to declare generic arguments separately in the method (because you can't add constraints if they belong to the class) which I think it is not possible in your case. – Selman Genç Nov 08 '14 at 11:13

2 Answers2

0

Botton line: this can be done but requires disproportionate amounts of overhead. So it's completely impractical.

In the end, I resolved this by adding two overloaded methods without either argument.


According to C# specification v.3.0, §B.2.7, type constraints can be put on single methods as well. But, these methods need to have the types the constraints are put on as their own type parameters. So, I was able to do the trick like this:

protected void Dissociate<KP,LP>(KP? primaryKey, LP? subKey)
    where KP:struct,K where LP:struct,L
{
    primaryToSubkeyMapping.Remove(primaryKey??subDictionary[subKey.Value]);
    subDictionary.Remove(subKey??primaryToSubkeyMapping[primaryKey.Value]);
}

But, now, I need to make four implementations for each combination of class and struct! And when I try to call them inside typeof(K).IsValueType-like checks, the compiler fails to get it as a hint for overload resolution and yields the corresponding error. Naming them differently only goes one step further: the compiler is not convinced that only one of them will ever be called in any concrete implementation and still fails argument type checks. Now, there are two choices:

  • Call the related implementation through reflection. Getting the generic type parameter objects for GetMethod(), then making a concrete method is gonna be a PINA - even if I simplify the former step by naming the four implementations differently;

  • Make four derived classes, each with a different name. This would screw up the class hierarchy.

    • To resolve the hierarchy schism (at the cost of Liskov's principle), I can make another class that would delegate to one of them. In the corresponding code, I'll face the same type check problems again and will have to resolve them in the same manner as the previous item.

    In brief, this way is completely unusable.

ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152
0

You can specify Nullable<T> as the parameter type to allow the overload. For example:

class A { }
struct B { }

class Program
{
    static void Main(string[] args)
    {
        A a = new A();
        B? b = new B();

        Method(a);
        Method(b);

        a = null;
        b = null;

        Method(a);
        Method(b);
    }

    static void Method<T>(T t) where T : class
    {
        Console.WriteLine("Reference type: " + (t != null ? "not " : "") + "null");
    }

    static void Method<T>(Nullable<T> t) where T : struct
    {
        Console.WriteLine("Nullable<T> type: " + (t != null ? "not " : "") + "null");
    }
}

In your own scenario, if the type parameters K and L can be independently a reference type or Nullable<T>, then you'll need four overloads, to take care of all the possible combinations.

You should be able to have all four overloads share a common implementation by handling the null tests in each overload and then calling a shared implementation method that doesn't care about nulls (i.e. can safely assume they've been addressed).

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • At the step you describe, I had the problem that I couldn't tell the compiler which overloaded method to use if I tried to call them from other code inside the same generic class. – ivan_pozdeev Nov 08 '14 at 18:51
  • Yes. It was not clear from the question that the caller was also generic. For overload resolution to work, the compiler will need to know at compile-time what types and constraints are at play, which wouldn't be possible if called from a generic type without any constraints. Sorry about that. – Peter Duniho Nov 08 '14 at 18:57
  • My method is `protected` which implies that with almost complete certainty. I didn't emphasize this though. – ivan_pozdeev Nov 08 '14 at 19:07
  • I didn't realize that `protected` implies a generic class. But I will try to keep that in mind in the future. ;) – Peter Duniho Nov 08 '14 at 19:13
  • Well, see: 1) my method uses generic type parameters (this i did specify) which means it's a part of a generic class; 2) `protected` means that it could only be called a) from the same class, b) from a derived generic class, c) from a derived class (which is most probably generic with the same parameters: other options are very uncommon), d) from wherever through reflection (which is even more uncommon and almost nonexistent if I have control over the code). It's just simple deduction, Watson! :^) – ivan_pozdeev Nov 08 '14 at 19:17
  • 1
    If you had seen all of the badly-written questions I've seen, you would understand that just because you didn't include type parameters in the method declaration, that doesn't actually mean the method itself isn't a generic method. But, whatever...if you're happy with the responses you've gotten, then your question is perfect. Don't worry about it. – Peter Duniho Nov 08 '14 at 19:19