Почему я не могу использовать абстрактные статические методы в C#?

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

Оставьте их открытыми, чтобы можно было вносить улучшения в будущем.

Mark Biek 23.09.2008 21:49

Я думаю, что вопрос заключается в том, что C# нужно другое ключевое слово именно для таких ситуаций. Вам нужен метод, возвращаемое значение которого зависит только от типа, для которого он вызывается. Вы не можете назвать это "статическим", если указанный тип неизвестен. Но как только тип станет известен, он станет статичным. Идея заключается в «неразрешенной статике» - она ​​еще не статична, но, как только мы узнаем тип получения, она станет. Это прекрасная концепция, поэтому программисты ее постоянно просят. Но это не совсем соответствовало тому, как дизайнеры думали о языке.

William Jockusch 20.03.2015 17:00

@WilliamJockusch что означает тип получения? Если я вызываю BaseClass.StaticMethod (), тогда BaseClass - единственный тип, который он может использовать для принятия решения. Но на этом уровне он абстрактен, поэтому метод не может быть решен. Если вы вместо этого правильно вызовете DerivedClass.StaticMethod, тогда базовый класс не имеет значения.

Martin Capodici 09.02.2016 06:51

В базовом классе метод не разрешен, и вы не можете его использовать. Вам нужен производный тип или объект (который, в свою очередь, будет иметь производный тип). Вы должны иметь возможность вызывать baseClassObject.Method () или DerivedClass.Method (). Вы не можете вызвать BaseClass.Method (), потому что это не дает вам типа.

William Jockusch 09.02.2016 08:56

Возможный дубликат Как реализовать виртуальные статические свойства?

peterh - Reinstate Monica 10.01.2018 14:12

Кстати, TextWriter.Null и StreamWriter.Null продемонстрировали аналогичную попытку обойти эту тему.

Ken Kin 16.01.2018 07:41
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
190
6
107 824
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

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

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

public static class Base
{
    public static virtual int GetNumber() { return 5; }
}

public static class Child1 : Base
{
    public static override int GetNumber() { return 1; }
}

public static class Child2 : Base
{
    public static override int GetNumber() { return 2; }
}

Если вы вызовете Base.GetNumber (), какой метод будет вызван? Какое значение вернулось? Довольно легко увидеть, что без создания экземпляров объектов наследование довольно сложно. Абстрактные методы без наследования - это просто методы, у которых нет тела, поэтому их нельзя вызывать.

Учитывая ваш сценарий, я бы сказал, что Base.GetNumber () вернет 5; Child1.GetNumber () возвращает 1; Child2.GetNumber () возвращает 2; Можете ли вы доказать, что я неправ, чтобы помочь мне понять ваши рассуждения? Спасибо

Luis Filipe 02.10.2008 20:30

То, что, по вашему мнению, Base.GetNumber () возвращает 5, означает, что вы уже понимаете, что происходит. При возврате базового значения наследование не происходит.

David Wengier 05.10.2008 02:54

Почему в мире Base.GetNumber () возвращает что-то еще, кроме 5? Это метод базового класса - там только 1 вариант.

Artem Russakovskii 17.06.2009 11:33

@ArtemRussakovskii: Предположим, у кого-то был int DoSomething<T>() where T:Base {return T.GetNumber();}. Было бы полезно, если бы DoSomething<Base>() мог вернуть пять, а DoSomething<Child2>() - два. Такая возможность была бы полезна не только для игрушечных примеров, но и для чего-то вроде class Car {public static virtual Car Build(PurchaseOrder PO);}, где каждый класс, производный от Car, должен был бы определять метод, который мог бы создать экземпляр с учетом заказа на поставку.

supercat 17.06.2013 00:23

Точно такая же «проблема» и с нестатическим наследованием.

Ark-kun 08.01.2014 13:18

Это вообще неверный ответ. Как говорили некоторые другие, Base.GetNumber () всегда должен возвращать 5. Потомки 1 и 2 должны вернуть 1 и 2 соответственно. Больше ничего не имеет смысла.

Antonio Rodríguez 24.08.2020 12:34

Чтобы добавить к предыдущим объяснениям, вызовы статических методов привязаны к определенному методу в время компиляции, что скорее исключает полиморфное поведение.

C# статически типизирован; вызовы полиморфных методов также связаны во время компиляции, как я понимаю, то есть CLR не остается решать, какой метод вызывать во время выполнения.

Adam Tolley 14.05.2011 01:23

Как вы думаете, как именно полиморфизм работает в среде CLR? Ваше объяснение просто исключило отправку виртуального метода.

Rytmis 14.05.2011 19:38

Это не такой полезный комментарий, каким мог бы быть. Я предложил («как я понимаю») полезную беседу, подумайте, может быть, вы могли бы предоставить немного больше содержания - видя, как люди приходят сюда в поисках ответов, а не оскорблений. Хотя, похоже, я могу быть виноват в том же - я действительно имел в виду приведенный выше комментарий как вопрос: разве C# не оценивает эти вещи во время компиляции?

Adam Tolley 18.05.2011 22:17

Извините, я не имел в виду оскорбление (хотя признаю, что отвечал немного резко ;-). Суть моего вопроса заключалась в том, есть ли у вас эти классы: class Base {public virtual void Method (); } class Derived: Base {общедоступный метод отмены void (); } и напишите так: Base instance = new Derived (); instance.Method (); информация о типе времени компиляции на сайте вызова состоит в том, что у нас есть экземпляр Base, тогда как фактический экземпляр является Derived. Таким образом, компилятор не может определить точный метод для вызова. Вместо этого он испускает инструкцию IL "callvirt", которая сообщает среде выполнения, что нужно выполнить диспетчеризацию.

Rytmis 19.05.2011 17:40

... в то время как для статических методов нет экземпляра для выполнения виртуальной отправки, поэтому он всегда обязательно выдает инструкцию "вызова". И теперь, когда я расширил свой ответ, я понимаю, какой это был дерьмовый ответ. ;-)

Rytmis 19.05.2011 17:42

Спасибо, чувак, это информативно! Думаю, я достаточно долго откладывал погружение в ИЛ, желаю удачи.

Adam Tolley 01.06.2011 19:44
Ответ принят как подходящий

Статические методы не являются создан как таковые, они просто доступны без ссылки на объект.

Вызов статического метода выполняется через имя класса, а не через ссылку на объект, и код промежуточного языка (IL) для его вызова вызовет абстрактный метод через имя класса, который его определил, не обязательно имя класс, который вы использовали.

Приведу пример.

Со следующим кодом:

public class A
{
    public static void Test()
    {
    }
}

public class B : A
{
}

Если вы вызовете B.Test, вот так:

class Program
{
    static void Main(string[] args)
    {
        B.Test();
    }
}

Тогда фактический код внутри метода Main выглядит следующим образом:

.entrypoint
.maxstack 8
L0000: nop 
L0001: call void ConsoleApplication1.A::Test()
L0006: nop 
L0007: ret 

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

Если бы у вас был типы классов, как в Delphi, где вы можете создать переменную, относящуюся к типу, а не объекту, вы бы больше использовали виртуальные и, следовательно, абстрактные статические методы (а также конструкторы), но они недоступны и, следовательно, статические вызовы в .NET не являются виртуальными.

Я понимаю, что разработчики IL могут позволить скомпилировать код для вызова B.Test и разрешить вызов во время выполнения, но он все равно не будет виртуальным, так как вам все равно придется писать там какое-то имя класса.

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

Таким образом, виртуальные / абстрактные статические методы недоступны в .NET.

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

Chris Moschini 20.03.2012 12:41

Я не считаю этот ответ очень полезным, поскольку определение Test() находится в A, а не является абстрактным и потенциально определенным в B. \

user610650 31.05.2012 19:48

Параметры универсального типа фактически ведут себя как непостоянные переменные «типа», и в таком контексте могут быть полезны виртуальные статические методы. Например, если у кого-то был тип Car с виртуальным статическим заводским методом CreateFromDescription, то код, который принимал общий тип Car с ограничениями T, мог бы вызвать T.CreateFromDescription для создания автомобиля типа T. Такая конструкция могла бы довольно хорошо поддерживаться в среде CLR, если бы каждый тип, который определял такой метод, содержал статический одноэлементный экземпляр универсального вложенного класса, который содержал виртуальные «статические» методы.

supercat 11.06.2013 02:27

Другой респондент (Макдауэлл) сказал, что полиморфизм работает только для экземпляров объектов. Это должно быть оговорено; есть языки, которые рассматривают классы как экземпляры типа «Класс» или «Метакласс». Эти языки поддерживают полиморфизм как для методов экземпляра, так и для методов класса (статических).

C#, как Java и C++ до него, не является таким языком; ключевое слово static используется явно для обозначения того, что метод является статически привязанным, а не динамическим / виртуальным.

На самом деле мы переопределяем статические методы (в delphi), это немного некрасиво, но отлично работает для наших нужд.

Мы используем его, чтобы классы могли иметь список своих доступных объектов без экземпляра класса, например, у нас есть метод, который выглядит так:

class function AvailableObjects: string; override;
begin
  Result := 'Object1, Object2';
end; 

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

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

Так что поддерживать это намного проще, чем иметь одно отдельное серверное приложение для каждого клиента.

Надеюсь, пример был ясен.

Абстрактные методы неявно виртуальны. Абстрактные методы требуют экземпляра, но статические методы не имеют экземпляра. Итак, у вас может быть статический метод в абстрактном классе, он просто не может быть статическим абстрактным (или абстрактным статическим).

-1 виртуальным методам не нужен экземпляр, кроме как по дизайну. И вы на самом деле не решаете вопрос, а даже отклоняете его.

FallenAvatar 10.11.2013 02:29

Вот ситуация, когда определенно необходимо наследование для статических полей и методов:

abstract class Animal
{
  protected static string[] legs;

  static Animal() {
    legs=new string[0];
  }

  public static void printLegs()
  {
    foreach (string leg in legs) {
      print(leg);
    }
  }
}


class Human: Animal
{
  static Human() {
    legs=new string[] {"left leg", "right leg"};
  }
}


class Dog: Animal
{
  static Dog() {
    legs=new string[] {"left foreleg", "right foreleg", "left hindleg", "right hindleg"};
  }
}


public static void main() {
  Dog.printLegs();
  Human.printLegs();
}


//what is the output?
//does each subclass get its own copy of the array "legs"?

Нет, есть только один экземпляр массива «ножки». Вывод является недетерминированным, поскольку вы не знаете, в каком порядке будут вызываться статические конструкторы (на самом деле нет никакой гарантии, что статический конструктор базового класса вообще будет вызван). «Потребность» - это довольно безусловный термин, более точный термин «желание».

Sam 18.02.2010 09:16

legs должен быть статическим абстрактным свойством.

Little Endian 05.08.2016 23:31

Этому вопросу 12 лет, но он все еще требует лучшего ответа. Как немногие отметили в комментариях и вопреки тому, что утверждают все ответы, безусловно, имеет смысл иметь статические абстрактные методы в C#. Как сказал философ Дэниел Деннетт, недостаток воображения - это не понимание необходимости. Распространенная ошибка - не осознавать, что C# - это не только язык ООП. Чистая точка зрения ООП на данную концепцию ведет к ограниченному и, в данном случае, ошибочному анализу. Полиморфизм - это не только разделение полиморфизма на подтипы: он также включает параметрический полиморфизм (также известный как универсальное программирование), и C# уже давно поддерживает это. В рамках этой дополнительной парадигмы абстрактные классы (и большинство типов) используются не только для ввода экземпляров. Их также можно использовать как границы для общих параметров; то, что было понятно пользователям определенных языков (например, Haskell, а в последнее время Scala, Rust или Swift) в течение многих лет.

В этом контексте вы можете сделать что-то вроде этого:

void Catch<TAnimal>() where TAnimal : Animal
{
    string scientificName = TAnimal.ScientificName; // abstract static property
    Console.WriteLine($"Let's catch some {scientificName}");
    …
}

И здесь возможность выражать статические члены, которые могут быть специализированы подклассами полностью имеет смысл!

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

Основная идея состоит в том, чтобы связать абстрактный сопутствующий класс (здесь SpeciesFor<TAnimal>) с тем, который должен содержать абстрактные члены (здесь Animal):

public abstract class SpeciesFor<TAnimal> where TAnimal : Animal
{
    public static SpeciesFor<TAnimal> Instance { get { … } }

    // abstract "static" members

    public abstract string ScientificName { get; }
    
    …
}

public abstract class Animal { … }

Теперь мы бы хотели поработать:

void Catch<TAnimal>() where TAnimal : Animal
{
    string scientificName = SpeciesFor<TAnimal>.Instance.ScientificName;
    Console.WriteLine($"Let's catch some {scientificName}");
    …
}

Конечно, нам нужно решить две проблемы:

  1. Как мы можем разрешить и заставить разработчика подкласса Animal связать конкретный экземпляр SpeciesFor<TAnimal> с этим подклассом?
  2. Как свойство SpeciesFor<TAnimal>.Instance получает эту информацию?

Вот как мы можем решить 1:

public abstract class Animal<TSelf> where TSelf : Animal<TSelf>
{
    private Animal(…) {}
    
    public abstract class OfSpecies<TSpecies> : Animal<TSelf>
        where TSpecies : SpeciesFor<TSelf>, new()
    {
        protected OfSpecies(…) : base(…) { }
    }
    
    …
}

Сделав конструктор Animal<TSelf> закрытым, мы гарантируем, что все его подклассы также являются подклассами внутреннего класса Animal<TSelf>.OfSpecies<TSpecies>. Таким образом, эти подклассы должны указывать тип TSpecies с привязкой к new().

Для 2 мы можем предоставить следующую реализацию:

public abstract class SpeciesFor<TAnimal> where TAnimal : Animal<TAnimal>
{
    private static SpeciesFor<TAnimal> _instance;

    public static SpeciesFor<TAnimal> Instance => _instance ??= MakeInstance();

    private static SpeciesFor<TAnimal> MakeInstance()
    {
        Type t = typeof(TAnimal);
        while (true)
        {
            if (t.IsConstructedGenericType
                    && t.GetGenericTypeDefinition() == typeof(Animal<>.OfSpecies<>))
                return (SpeciesFor<TAnimal>)Activator.CreateInstance(t.GenericTypeArguments[1]);
            t = t.BaseType;
            if (t == null)
                throw new InvalidProgramException();
        }
    }

    // abstract "static" members

    public abstract string ScientificName { get; }
    
    …
}

Как мы можем быть уверены, что код отражения внутри MakeInstance() никогда не сработает? Как мы уже говорили, почти все классы в иерархии Animal<TSelf> также являются подклассами Animal<TSelf>.OfSpecies<TSpecies>. Итак, мы знаем, что для этих классов должен быть предусмотрен специальный TSpecies. Этот тип также обязательно может быть сконструирован благодаря ограничению : new(). Но при этом по-прежнему остаются абстрактные типы, такие как Animal<Something>, не имеющие ассоциированных видов. Теперь мы можем убедиться, что любопытно повторяющийся шаблон шаблона where TAnimal : Animal<TAnimal> делает невозможным запись SpeciesFor<Animal<Something>>.Instance, поскольку тип Animal<Something> никогда не является подтипом Animal<Animal<Something>>.

И вуаля:

public class CatSpecies : SpeciesFor<Cat>
{
    // overriden "static" members

    public override string ScientificName => "Felis catus";
    public override Cat CreateInVivoFromDnaTrappedInAmber() { … }
    public override Cat Clone(Cat a) { … }
    public override Cat Breed(Cat a1, Cat a2) { … }
}

public class Cat : Animal<Cat>.OfSpecies<CatSpecies>
{
    // overriden members

    public override string CuteName { get { … } }
}

public class DogSpecies : SpeciesFor<Dog>
{
    // overriden "static" members

    public override string ScientificName => "Canis lupus familiaris";
    public override Dog CreateInVivoFromDnaTrappedInAmber() { … }
    public override Dog Clone(Dog a) { … }
    public override Dog Breed(Dog a1, Dog a2) { … }
}

public class Dog : Animal<Dog>.OfSpecies<DogSpecies>
{
    // overriden members

    public override string CuteName { get { … } }
}

public class Program
{
    public static void Main()
    {
        ConductCrazyScientificExperimentsWith<Cat>();
        ConductCrazyScientificExperimentsWith<Dog>();
        ConductCrazyScientificExperimentsWith<Tyranosaurus>();
        ConductCrazyScientificExperimentsWith<Wyvern>();
    }
    
    public static void ConductCrazyScientificExperimentsWith<TAnimal>()
        where TAnimal : Animal<TAnimal>
    {
        // Look Ma! No animal instance polymorphism!
        
        TAnimal a2039 = SpeciesFor<TAnimal>.Instance.CreateInVivoFromDnaTrappedInAmber();
        TAnimal a2988 = SpeciesFor<TAnimal>.Instance.CreateInVivoFromDnaTrappedInAmber();
        TAnimal a0400 = SpeciesFor<TAnimal>.Instance.Clone(a2988);
        TAnimal a9477 = SpeciesFor<TAnimal>.Instance.Breed(a0400, a2039);
        TAnimal a9404 = SpeciesFor<TAnimal>.Instance.Breed(a2988, a9477);
        
        Console.WriteLine(
            "The confederation of mad scientists is happy to announce the birth " +
            $"of {a9404.CuteName}, our new {SpeciesFor<TAnimal>.Instance.ScientificName}.");
    }
}

Ограничение этого шаблона состоит в том, что невозможно (насколько я могу судить) расширить иерархию классов удовлетворительным образом. Например, мы не можем ввести промежуточный класс Mammal, связанный с компаньоном MammalClass. Во-вторых, он не работает для статических членов в интерфейсах, которые были бы более гибкими, чем абстрактные классы.

Другие вопросы по теме