Почему я не могу неявным образом привести делегат с помощью методов Extension? - PullRequest
7 голосов
/ 13 февраля 2009

Я пытаюсь найти способ автоматически приводить что-то к Action или Func, и лучшее, что я могу придумать, это что-то вроде этого:

[TestFixture]
public class ExecutionTest
{
    public void BadMethod()
    {
        throw new Exception("Something bad happened");
    }

    [Test]
    public void TestBadMethod()
    {
        // Want this, but it won't work!!
        // BadMethod.Execute().IgnoreExceptions();

        // Ick
        ((Action)BadMethod).Exec().IgnoreExceptions();

        // Still ick
        ((Action)BadMethod).IgnoreExceptions();

        // Do not want
        ExtensionMethods.Exec(BadMethod).IgnoreExceptions();

        // Better but still meh
        this.Exec(BadMethod).IgnoreExceptions();

    }
}

public static class ExtensionMethods
{
    public static Action Exec(this Action action)
    { return action; }

    public static Action Exec(this object obj, Action action)
    { return action; }

    public static void IgnoreExceptions(this Action action)
    {
        try { action(); }
        catch {}
    }
}

Есть ли лучший способ сделать это? Есть мысли?

Ответы [ 3 ]

5 голосов
/ 13 февраля 2009

В C #, когда вы используете имя метода без круглых скобок, оно называется группой методов и не имеет представления, кроме как во время компиляции. Группа методов может представлять более одного метода (из-за перегрузок и переопределений), поэтому для неявного определения, какой метод необходим, должен быть указан целевой тип делегата.

В вашем случае вам интересно, почему тип параметра метода расширения не будет вызывать разрешение функции. Проще говоря, расширение оценивается после того, как тип известен, то есть этот параметр нельзя использовать в качестве цели неявного преобразования.

Пример того, почему он сломается:

class Test
{
    void M (void) // Fits Action delegate
    {
    }

    int M (int) // Fits Func<int,int> delegate
    {
        return 5;
    }

    void Test()
    {
        M.Exec(); // UHOH!!! Which Exec to resolve to ???
    }
}


public static class Extensions
{
    public static void Exec(this Action action) { }
    public static void Exec(this Func<int, int> func) { }
}

Как видите, конфликт существует, но на самом деле конфликт никогда не происходит, потому что C # даже не пытается найти соответствующее расширение с группой методов.

Обратите внимание, как это не будет работать:

class A
{
    public static implicit operator int (A a)
    {
        return 5;
    }

    void F()
    {
       A a = new A();
       a.Blah(); // Error! It won't implicitly try C.Blah()
    }
}

public static class C
{
    public static void Blah (int i)
    {
    }
}

C # не будет совпадать с A до C.Blah(int), поскольку для этого потребуется неявное преобразование.

2 голосов
/ 13 февраля 2009

Как говорит Coincoin, в C # это не будет хорошо работать из-за чрезмерной любви к перегрузке методов. Единственный обходной путь, который я видел у людей, - это создание методов Action и Func:

public Action Action(Action f) { return f; }
public Action<A> Action<A>(Action<A> f) { return f; }
...
public Func<A,B,C,D,E> Func(Func<A,B,C,D,E> f) { return f; }

Вы могли бы даже назвать их всех "F", чтобы получить некоторый краткий синтаксис:

F(BadMethod).NoExceptions();

Вы можете решить не определять эти методы в своем классе и поместить их в утилиту Funcs или что-то в этом роде. Псевдоним это с F, и это не так уж плохо:

F.F(BadMethod).NoException();

Но в целом это все еще отстой :(.

1 голос
/ 08 января 2010

F # позволяет вам делать такие вещи очень естественно, предоставляя гораздо лучшую систему вывода типов.

...