Чому я не можу використовувати абстрактні статичні методи в C #?


182

Я працював із Останнім часом провайдерами досить небагато, і мені трапилось цікаве становище, коли я хотів мати абстрактний клас, який мав абстрактний статичний метод. Я прочитав кілька публікацій на цю тему, і це мало сенс, але чи є приємне чітке пояснення?


3
Будь ласка, залиште їх відкритими, щоб дозволити майбутні удосконалення
Марк Бік

6
Я думаю, що питання виникає в тому, що C # потребує іншого ключового слова, саме для такої ситуації. Ви хочете, метод, повернене значення якого залежить лише від типу, на який він викликається. Ви не можете назвати це "статичним", якщо вказаний тип невідомий. Але як тільки тип стане відомим, він стане статичним. Ідея "невирішене статичне" - це ще не статично, але як тільки ми дізнаємось тип отримання, він буде. Це ідеально хороша концепція, саме тому програмісти продовжують просити її. Але це не зовсім вписувалося в те, як дизайнери думали про мову.
Вільям Йокуш

@WilliamJockusch, що означає тип прийому? Якщо я називаю BaseClass.StaticMethod (), тоді BaseClass - єдиний тип, який він може використовувати для прийняття рішення. Але на цьому рівні це абстрактно, тому метод неможливо вирішити. Якщо ви замість цього добре викликаєте DerivedClass.StaticMethod, базовий клас не має значення.
Мартін Каподічі

У базовому класі метод не вирішений, і ви не можете його використовувати. Вам потрібен або похідний тип, або об'єкт (який у свою чергу мав би похідний тип). Ви повинні мати можливість викликати baseClassObject.Method () або DerivedClass.Method (). Ви не можете зателефонувати BaseClass.Method (), оскільки це не дає вам тип.
Вільям Йокуш

Відповіді:


157

Статичні методи не інстанціювати як такі, вони просто доступні без посилання на об'єкт.

Виклик статичного методу здійснюється через ім'я класу, а не через посилання на об'єкт, і код проміжного мови (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.


4
У поєднанні з способом перевантаження оператора в C #, це, на жаль, виключає можливість вимагати від підкласів, щоб забезпечити реалізацію для певного перевантаження оператора.
Кріс Москіні

23
Я не вважаю цю відповідь надзвичайно корисною, оскільки визначення Test()є Aзамість того, щоб бути абстрактним і потенційно визначеним в B. \

5
Параметри загального типу ефективно поводяться як непостійні змінні "типу", і віртуальні статичні методи можуть бути корисні в такому контексті. Наприклад, якщо у вас був Carтип з віртуальним статичним CreateFromDescriptionзаводським методом, то код, який приймав Car-обмежений загальний тип, Tможе закликати T.CreateFromDescriptionвиробляти машину типу T. Таку конструкцію можна було б досить добре підтримувати в CLR, якби кожен тип, який визначав такий метод, містив статичний однотонний екземпляр вкладеного загального класу, який містив віртуальні "статичні" методи.
supercat

45

Статичні методи не можуть бути успадковані або відмінені, і тому вони не можуть бути абстрактними. Оскільки статичні методи визначені для типу, а не екземпляра класу, їх потрібно чітко викликати для цього типу. Отже, коли ви хочете викликати метод у дочірньому класі, для його виклику потрібно використовувати його ім’я. Це робить спадщину не важливою.

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

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 (), який би метод називався? Яке значення повернулося? Це досить легко помітити, що без створення екземплярів об'єктів успадкування досить важко. Абстрактні методи без успадкування - це лише методи, які не мають тіла, тому їх не можна назвати.


33
Враховуючи ваш сценарій, я б сказав, що Base.GetNumber () поверне 5; Child1.GetNumber () повертає 1; Child2.GetNumber () повертає 2; Чи можете ви довести мене неправильно, щоб допомогти зрозуміти ваші міркування? Дякую
Луїс Філіпе

Обличчя, яке, на вашу думку, повертає Base.GetNumber () 5, означає, що ви вже розумієте, що відбувається. Повертаючи базову вартість, спадкування не відбувається.
Девід Венг'є

60
Чому б у світі Base.GetNumber () повертав що-небудь інше, крім 5? Це метод в базовому класі - там є лише 1 варіант.
Артем Русаковський,

4
@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

4
Існує точно така ж "проблема" з нестатичним успадкуванням.
Арк-кун

18

Інший респондент (МакДауелл) сказав, що поліморфізм працює лише для предметів. Це слід кваліфікувати; Є мови, які розглядають класи як екземпляри типу "Class" або "Metaclass". Ці мови підтримують поліморфізм як для екземплярних, так і класових (статичних) методів.

C #, як і Java та C ++ перед цим, не є такою мовою; staticключове слово використовується для позначення явно , що метод статичний палітуркою , а не динамічний / віртуальний.


9

Ось ситуація, коли безумовно необхідність успадкування статичних полів та методів:

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"?

4
Ні, є лише один екземпляр масиву "ноги". Вихід недетермінований, оскільки ви не знаєте, яким порядком будуть називатися статичні конструктори (фактично немає гарантії статичного конструктора базового класу взагалі називатися). "Потреба" - це абсолютно абсолютний термін, де "бажання", ймовірно, більш точне.
Сем

legsмає бути статичною абстрактною властивістю.
Little Endian

8

Щоб додати до попередніх пояснень, статичні виклики методів прив'язуються до конкретного методу під час компіляції , який, швидше, виключає поліморфну ​​поведінку.


C # статично набраний; дзвінки до поліморфних методів також пов'язані під час компіляції, наскільки я це розумію - тобто CLR не залишається вирішувати, який метод викликати під час виконання.
Адам Толлі

То як, на вашу думку, поліморфізм працює на CLR? Ваше пояснення просто виключило відправлення віртуального методу.
Ритміс

Це насправді не такий корисний коментар, як міг би бути. Я запропонував (з "я так розумію") корисний дискурс, подумайте, може, ви могли б надати трохи більше контенту - бачачи, як люди приходять сюди, шукаючи відповіді, а не образи. Хоча, здається, я можу бути винним у тому самому - я справді мав на увазі вищезазначений коментар як питання: чи не оцінює C # ці речі під час компіляції?
Адам Толлі

Вибачте, я не мав на увазі образи (хоча я зізнаюся, що відповів трохи спритно ;-). Суть мого запитання полягала в тому, що якщо у вас є такі класи: клас Base {public virtual void Method (); } клас Отримано: Base {public override void Method (); } і запишіть таким чином: Base instance = new Derived (); instance.Method (); Інформація про тип компіляції на сайті виклику полягає в тому, що у нас є екземпляр Base, коли фактичний екземпляр є Похідним. Тож компілятор не може вирішити точний метод виклику. Натомість він випромінює ІЛ-інструкцію "callvirt", яка повідомляє час відправки.
Ритміс,

1
Спасибі людино, це інформативно! Здогадуюсь, я досить довго відкладаю занурення в Іллі, бажайте мені удачі.
Адам Толлі

5

Ми фактично перекриваємо статичні методи (у delphi), це трохи некрасиво, але це працює чудово для наших потреб.

Ми використовуємо це, щоб класи могли мати список доступних об'єктів без екземпляра класу, наприклад, у нас є метод, який виглядає приблизно так:

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

Це некрасиво, але необхідно, таким чином ми можемо інстанціювати лише те, що потрібно, замість того, щоб усі класи були миттєвими лише для пошуку доступних об'єктів.

Це був простий приклад, але сама програма є клієнтсько-серверним додатком, у якому є всі класи, доступні лише на одному сервері, і багато різних клієнтів, яким може не знадобитися все, що має сервер, і ніколи не знадобиться об’єкт об'єкта.

Тож це набагато простіше в обслуговуванні, ніж наявність одного серверного додатку для кожного клієнта.

Сподіваюся, що приклад був зрозумілий.


0

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


1
-1 віртуальним методам екземпляр не потрібен, за винятком дизайну. І ви насправді не вирішуєте питання, настільки, як відхиляєте його.
FallenAvatar
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.