Using a generic parameter on overloaded methods

随声附和 提交于 2021-02-07 19:12:00

问题


Why does this program output Generic Value and not Hello world!:

using System;

class Example
{
    public static void Print<T>(T value)
    {
        Console.WriteLine("Generic Value");
    }

    public static void Print(string value)
    {
        Console.WriteLine(value);
    }

    public static void GenericFunc<T>(T value)
    {
        Print(value);
    }

    static void Main()
    {
        GenericFunc("Hello world!");
    }
}

How is the generic method parameter being translated under the hood of C#?


回答1:


Overload resolution is only done at compile-time.

Since GenericFunc<T> doesn't know whether T is a string or something else at compile-time, it can only ever use the Print<T>(T value) "overload".

Using dynamic, you can change this to a dynamic dispatch, and get the behaviour you expect:

Print((dynamic)value);

This makes the overload resolution happen at runtime, with the actual runtime type of value.




回答2:


Simple answer to simple question

The other answer explains the way generic are bound (compile-time). But it doesn't answer on the OOP, good practice, or simply why you should never write this code in the first place.

OOP

The first O in OOP means Object and there is none with only static methods.

Responsibility

Let's think about the Generic versin of the method as a method responsible for printing a set of different possible types. String type is part of the set. So it should be managed by the generic version of your Print function.

public static void Print<T>(T value)
{
    Console.WriteLine(value.ToString());
}

then you got the problem of nullity for ref types.

    public static void Print<T>(T value) where T : class
    {
        if (value != null)
        {
            Console.WriteLine(value.ToString());
        }
    }

    public static void GenericFunc<T>(T value) where T : class
    {
        Print(value);
    }

And for those who are aware of why you should not use dynamic unless in some cases (see my anwer on that).

More clean OOP solution

Now imagine you have different objects to print. Each object should be responsible for knowing how to display it. Firstly, because it ease encapsulation of data by not leaking internal data to the external world. Secondly, because you've got an inherent coupling between the internal data and the printing function, so both should be located in the same place: inside the class. That's the purpose of the ToString function.

Let's take some height...

Now, we could imagine that it's not a Print function but something else.

We got a hierarchy of classes with overload on the same function (let's call it Foo) and a collection of instances from these classes for which you must call the function Foo. Let's then make all these classes implement the IFooCallable interface :

public interface IFooCallable
{
    void Foo();
}

A little more complex...

Ok, but imagine that there is not common way to process all these instances of classes because there are very different.

Let's call the Visitor pattern. It's commonly used when you want to analyze some object tree with each node very different (like in AST). It's a known pattern, making it easy to share knowledgable information with your team.

You got the Visitor:

public class Visitor : IVisitor
{
    public void Visit(Foo foo)
    {
        // do something with foo
    }

    public void Visit(Bar bar)
    {
        // do something with bar
    }
}

and the Visitable

public class Foo : IVisitable
{
    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

Moreover this pattern is reusable (you could write several implementation of IVisitor should you need to).

I don't buy the dynamic thing. Especially when there is more cleaner, faster alternative. If dynamic so great, why not writing this then ;)

    public static void Print(dynamic value)
    {
        Console.WriteLine(value);            
    }

    public static void GenericFunc(dynamic value)
    {
        Print(value);
    }

    static void Main(dynamic[] args)
    {
        GenericFunc((dynamic)"Hello World");
    }


来源:https://stackoverflow.com/questions/35340138/using-a-generic-parameter-on-overloaded-methods

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!