Выведение только одного типа с несколькими обобщенными c типами - PullRequest
0 голосов
/ 12 марта 2020

У меня есть метод generic c, определенный следующим образом:

public static A Test<A, B>(B b)
    where A : new()
{
    return new A();
}

Я бы хотел назвать его примерно так:

int result = Test<int>(5.0);

Вместо как this:

int result = Test<int, double>(5.0);

Очевидно, что синтаксис языка не допускает чего-то подобного. Я ищу простой способ сделать это, так как тип B, в моем случае, обычно будет чем-то длинным, и я хотел бы избежать длинных строк кода, которые в основном являются просто вызовами методов.

Нечто подобное выполнимо, но безобразно:

A Test<A, B>(B b, A placeholder) where A : new() => new A(); // Definition

int result = Test(5.0, 2); // Method call

Есть ли другие предложения для этого?

Ответы [ 2 ]

1 голос
/ 12 марта 2020

В соответствии с комментарием-ответом @ Damien_The_Unbeliever, тип-вывод C# не поддерживает частичный вывод - либо все параметры (и тип возврата, если применимо) должны быть выведены из вызова -site - или вы должны вручную указать все параметры типа.

Хотя во многих случаях есть обходные пути:

Stati c методы с возможными логическими аргументами типа:

Если у вас есть фабричный метод stati c в обобщенном классе c, вы можете переместить метод в класс stati c и переместить аргумент типа родительского класса в метод, если он может быть выведен:

public class Foo<T>
{
    public static Bar CreateBar( T item )
    {
        // ...
    }
}

Пример call-сайта:

Bar bar = Foo<Coffee>.Bar( starbucks ); 

Альтернатива:

public static class Foo
{
    public public static Bar CreateBar<T>( T item )
    {
        // ...
    }
}

Пример call-сайта:

Bar bar = Foo.Bar( starbucks );  // voila, type-inference!

Методы с не- аргументы заразного типа:

Методы с аргументами типа, которые не могут быть выведены из сайта вызова, могут быть преобразованы в новые обобщенные c методы с частичным применением параметров, например:

Рассмотрим :

class Foo<TClass>
{
    public TReturn DoSomething<TParam,TUnused,TReturn>( TParam p )
    {
        // ...
    }
}

E xample call-site:

Violin stradivarius = ...
Woodwind flute = new Foo<Orchestra>().DoSomething<Violin,Percussion,Woodwind>( stradivarius ); // `Violin` was required and couldn't be inferred.

Однако мы можем заключить этот метод DoSomething в другой вызов метода, где некоторые аргументы типа уже предоставлены родительским контекстом , таким как аргументы типа родительского класса или как аргументы типа для методов класса 'stati c только с типами для параметров, которые могут быть выведены.

Таким образом, вы можете сортировать частично применить эти общие c типы с Func<>, например так:

class PAReturn<TReturn>
{
    public static TReturn Invoke( Func<TReturn> func ) => func();

    public static TReturn Invoke<T0>( Func<T0,TReturn> func, T0 arg ) => func( arg );

    public static TReturn Invoke<T0,T1>( Func<T0,T1,TReturn> func, T0 arg, T1 arg1 ) => func( arg, arg1 );

    public static TReturn Invoke<T0,T1,T2>( Func<T0,T1,T2,TReturn> func, T0 arg, T1 arg1, T2 arg2 ) => func( arg, arg1, arg2 );

    // etc
}

class PAReturn<TReturn,T0>
{
    public static TReturn Invoke( Func<T0,TReturn> func, T0 arg ) => func( arg );

    public static TReturn Invoke<T1>(Func<T0, T1, TReturn> func, T0 arg, T1 arg1) => func(arg, arg1);

    public static TReturn Invoke<T1,T2>(Func<T0, T1, T2, TReturn> func, T0 arg, T1 arg1, T2 arg2) => func( arg, arg1, arg2 );
}

Пример call-сайта:

Violin stradivarius = ...
Woodwind flute = PartialAply<Percussion,Woodwind>( new Foo<Orchestra>().DoSomething )( stradivarius ); // Observe that `Violin` was inferred.

Неиспользуемые параметры:

Еще один прием Воспользуйтесь преимуществами того, как вывод типов лучше всего работает для параметров, создавая перегрузки с неиспользуемыми out параметрами, которые можно указать с помощью способности C# 7.0 делать объявления внутри аргументов out параметров в call-сайтах и ​​как переменные / параметры именуются _ отбрасываются:

class Foo<A>
{
    // Original method:
    public B GetSomething<B,C,D>( C paramC )
    {
        // ...
    }
}

Пример call-сайта:

Cat bagheera = ...
Mouse m = new Foo<Flea>().GetSomething<Mouse,Cat,Dog>( bagheera ); // `Cat` was not inferred.

Примерно так:

partial class Foo<A>
{
    // Inference helper overload:
    public B GetSomething<B,C,D>( out B b, out D d, C c)
    {
        return this.GetSomething<B,C,D>( c );
    }
}

Пример call-сайта:

Cat bagheera = ...
Mouse m = new Foo<Flea>().GetSomething( out Mouse _, out Dog _, bagheera ); // `Cat` was inferred.
Комбинировано:

Это может быть объединено с новым дель Например, определения с параметрами out для параметров типа, не подлежащих замене (потому что мы не можем использовать Func<>, потому что в нем нет параметров out):

delegate TReturn PAFunc<TReturn>( out Return _ );
delegate TReturn PAFunc<T0,TReturn>( out Return _, T0 arg0 );

delegate TReturn PAFunc<T0,T1,TReturn>( out Return _, T0 arg0, T1 arg1 );

delegate TReturn PAFunc<T0,T1,N0,TReturn>( out Return _, out N0 _, T0 arg0 ); // `N0` refers to which one is non-inferrable
// etc... 
0 голосов
/ 12 марта 2020

Вы не можете на самом деле, и вот почему .... учитывая ниже, что работает, теперь, если вы хотите сделать б вывод ... .... 1001 *

public class MyFancyClass
{

}
public static class Test
{
    public static A Method<A>(**string b**) where A : new()
    {
        return new A();
    }
}
static async Task Main(string[] args)
{
   var result = Test.Method<MyFancyClass>("5.0");
}

если вы изменили **string b** если вы сделаете вывод типа, вы получите обратно то, что имели. как если бы вы хотели опустить тип, то как бы компилятор узнал тип.

вы могли бы сделать (не предлагая просто сказать)

public static A Method<A>(**object**) where A : new()
{
    return new A();
}

, но опять же вы не знали бы его тип , поэтому вам необходимо установить флажок, чтобы распаковать его в его тип.

if( b is string)
{
}
else if(b is int)
{
}
else if (b is double)
{
}
.... and continue.

как

public static class Test
{
    public static A Method<A>(object b) where A : new()
    {
        if (b is string)
        {
        }
        else if (b is int)
        {
        }
        else if (b is double)
        {
        }

        return new A();
    }
}
...