Вызов статического метода для параметра универсального типа

107

Я надеялся сделать что-то подобное, но в C # это оказалось незаконным:


public Collection MethodThatFetchesSomething<T>()
    where T : SomeBaseClass
{
    return T.StaticMethodOnSomeBaseClassThatReturnsCollection();
}

Я получаю ошибку времени компиляции: «'T' - это 'параметр типа', который недопустим в данном контексте».

Учитывая параметр универсального типа, как я могу вызвать статический метод для универсального класса? Статический метод должен быть доступен с учетом ограничения.

Реми Депре-Смит
источник
4
См. Blogs.msdn.com/b/ericlippert/archive/2007/06/14/… и blogs.msdn.com/b/ericlippert/archive/2007/06/18/… и blogs.msdn.com/b/ ericlippert / archive / 2007/06/21 / 3445650.aspx для получения дополнительной информации по этой теме.
Эрик Липперт

Ответы:

59

В этом случае вы должны просто вызвать статический метод для ограниченного типа напрямую. C # (и CLR) не поддерживают виртуальные статические методы. Так:

T.StaticMethodOnSomeBaseClassThatReturnsCollection

... не может отличаться от:

SomeBaseClass.StaticMethodOnSomeBaseClassThatReturnsCollection

Перебор параметра универсального типа является ненужным косвенным обращением и поэтому не поддерживается.

ДжаредПар
источник
25
Но что, если вы замаскируете свой статический метод в дочернем классе? открытый класс SomeChildClass: SomeBaseClass {общедоступный новый статический StaticMethodOnSomeBaseClassThatReturnsCollection () {}} Не могли бы вы сделать что-нибудь для доступа к этому статическому методу из универсального типа?
Hugo Migneron
2
Посмотрите ответ Джошуа Печ ниже, я считаю, что в этом случае это сработает.
Реми Депре-Смит
1
Будет return SomeBaseClass.StaticMethodOnSomeBaseClassThatReturnsCollection();работать? Если да, вы можете добавить это к своему ответу. Спасибо. У меня это сработало. В моем случае у моего класса был параметр типа, поэтому я это сделал, return SomeBaseClass<T>.StaticMethodOnSomeBaseClassThatReturnsCollection();и это сработало.
toddmo
27

Чтобы уточнить предыдущий ответ, я думаю, что размышление ближе к тому, что вы хотите здесь. Я мог бы назвать 1001 причину, почему вы должны или не должны что-то делать, я просто отвечу на ваш вопрос, как и было задано. Я думаю, вам следует вызвать метод GetMethod для типа универсального параметра и перейти оттуда. Например, для функции:

public void doSomething<T>() where T : someParent
{
    List<T> items=(List<T>)typeof(T).GetMethod("fetchAll").Invoke(null,new object[]{});
    //do something with items
}

Где T - любой класс, имеющий статический метод fetchAll ().

Да, я знаю, что это ужасно медленно и может привести к сбою, если someParent не заставит все свои дочерние классы реализовать fetchAll, но ответит на заданный вопрос.

Джошуа Печ
источник
2
Нет, совсем нет. JaredPar понял это абсолютно правильно: T.StaticMethodOnSomeBaseClassThatReturnsCollection, где T: SomeBaseClass ничем не отличается от простого указания SomeBaseClass.StaticMethodOnSomeBaseClassThatReturnsCollection.
Реми Депре-Смит,
2
Это то, что мне нужно, работает статическим методом
myro
Это был ответ, который мне был нужен, потому что у меня не было контроля над классами и базовым классом.
Тим
8

Единственный способ вызвать такой метод - это отражение, однако похоже, что можно было бы обернуть эту функциональность в интерфейс и использовать шаблон IoC / factory / etc на основе экземпляров.

Марк Гравелл
источник
5

Похоже, вы пытаетесь использовать дженерики, чтобы обойти тот факт, что в C # нет «виртуальных статических методов».

К сожалению, это не сработает.

Брэд Уилсон
источник
1
Я не - я работаю над созданным слоем DAL. Все сгенерированные классы наследуются от базового класса, у которого есть статический метод FetchAll. Я пытаюсь уменьшить дублирование кода в моем классе репозитория с помощью «универсального» класса репозитория - много повторяющегося кода, за исключением используемого конкретного класса.
Реми Депре-Смит,
1
Тогда почему бы вам просто не вызвать SomeBaseClass.StaticMethod ... ()?
Брэд Уилсон,
Извините, я не очень хорошо объяснил себя в предыдущем комментарии. FetchAll определяется на основе, но реализуется на производных классах. Мне нужно вызвать его в производном классе.
Реми Депре-Смит,
7
Если это статический метод, то он определяется и реализуется базой. В C # нет такого понятия, как виртуальный / абстрактный статический метод, и нет его переопределения. Я подозреваю, что вы просто повторно заявили об этом, а это совсем другое.
Марк Гравелл
1
Да, вы правы - я сделал здесь неверные предположения. Спасибо за обсуждение, это помогло мне встать на правильный путь.
Реми Депре-Смит,
2

На данный момент вы не можете. Вам нужен способ сообщить компилятору, что у T есть этот метод, и в настоящее время нет способа сделать это. (Многие подталкивают Microsoft к расширению того, что может быть указано в общем ограничении, поэтому, возможно, это станет возможным в будущем).

Джеймс Карран
источник
1
Проблема в том, что, поскольку универсальные шаблоны предоставляются средой выполнения, это, вероятно, означало бы новую версию CLR, которой они (в основном) избегали начиная с 2.0. Хотя, может быть, нам предстоит новый ...
Марк Грейвелл
2

Здесь я публикую пример, который работает, это обходной путь

public interface eInterface {
    void MethodOnSomeBaseClassThatReturnsCollection();
}

public T:SomeBaseClass, eInterface {

   public void MethodOnSomeBaseClassThatReturnsCollection() 
   { StaticMethodOnSomeBaseClassThatReturnsCollection() }

}

public Collection MethodThatFetchesSomething<T>() where T : SomeBaseClass, eInterface
{ 
   return ((eInterface)(new T()).StaticMethodOnSomeBaseClassThatReturnsCollection();
}
rodrijp
источник
2
Это дает мне синтаксическую ошибку? Что public T : SomeBaseClassзначит?
Эрик
Если в вашем классе есть метод экземпляра someInstanceMethod (), вы всегда можете вызвать его, выполнив (new T ()). SomeInstanceMethod (); - но это вызов метода экземпляра - задан вопрос, как вызвать статический метод класса.
тимофеевка
2

Я просто хотел сказать, что иногда делегаты решают эти проблемы, в зависимости от контекста.

Если вам нужно вызвать статический метод как своего рода фабрику или метод инициализации, вы можете объявить делегат и передать статический метод соответствующей универсальной фабрике или тому, что требуется для этого «универсального класса с этим статическим методом».

Например:

class Factory<TProduct> where TProduct : new()
{
    public delegate void ProductInitializationMethod(TProduct newProduct);


    private ProductInitializationMethod m_ProductInitializationMethod;


    public Factory(ProductInitializationMethod p_ProductInitializationMethod)
    {
        m_ProductInitializationMethod = p_ProductInitializationMethod;
    }

    public TProduct CreateProduct()
    {
        var prod = new TProduct();
        m_ProductInitializationMethod(prod);
        return prod;
    }
}

class ProductA
{
    public static void InitializeProduct(ProductA newProduct)
    {
        // .. Do something with a new ProductA
    }
}

class ProductB
{
    public static void InitializeProduct(ProductB newProduct)
    {
        // .. Do something with a new ProductA
    }
}

class GenericAndDelegateTest
{
    public static void Main()
    {
        var factoryA = new Factory<ProductA>(ProductA.InitializeProduct);
        var factoryB = new Factory<ProductB>(ProductB.InitializeProduct);

        ProductA prodA = factoryA.CreateProduct();
        ProductB prodB = factoryB.CreateProduct();
    }
}

К сожалению, вы не можете обеспечить, чтобы у класса был правильный метод, но вы можете, по крайней мере, во время компиляции обеспечить, чтобы в результирующем заводском методе было все, что он ожидал (т.е. метод инициализации с точно правильной подписью). Это лучше, чем исключение отражения во время выполнения.

Этот подход также имеет некоторые преимущества, например, вы можете повторно использовать методы инициализации, сделать их методами экземпляра и т. Д.

Амир Абири
источник
1

Вы можете сделать это с помощью отражения, как описано здесь.

Из-за того, что ссылка не работает, я нашел соответствующие детали в машине обратного пути:

Предположим, у вас есть класс со статическим универсальным методом:

class ClassWithGenericStaticMethod
{
    public static void PrintName<T>(string prefix) where T : class
    {
        Console.WriteLine(prefix + " " + typeof(T).FullName);
    }
}

Как можно вызвать этот метод с помощью отражения?

Оказывается, это очень просто ... Вот как вы вызываете статический универсальный метод с помощью Reflection:

// Grabbing the type that has the static generic method
Type typeofClassWithGenericStaticMethod = typeof(ClassWithGenericStaticMethod);

// Grabbing the specific static method
MethodInfo methodInfo = typeofClassWithGenericStaticMethod.GetMethod("PrintName", System.Reflection.BindingFlags.Static | BindingFlags.Public);

// Binding the method info to generic arguments
Type[] genericArguments = new Type[] { typeof(Program) };
MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(genericArguments);

// Simply invoking the method and passing parameters
// The null parameter is the object to call the method from. Since the method is
// static, pass null.
object returnValue = genericMethodInfo.Invoke(null, new object[] { "hello" });
Джон
источник
Ссылка мертвая.
Necronomicron