Прямое приведение против оператора "как"?


710

Рассмотрим следующий код:

void Handler(object o, EventArgs e)
{
   // I swear o is a string
   string s = (string)o; // 1
   //-OR-
   string s = o as string; // 2
   // -OR-
   string s = o.ToString(); // 3
}

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


1
Не совсем дубликат, но в предыдущем вопросе также есть обсуждения производительности .
Неразрезанный

8
4-й string s = Convert.ToString(o):; 5-е: string s = $"{o}"(или, что эквивалентно, string.Formatформа для более раннего C #)
Earth Engine

Ответы:


834
string s = (string)o; // 1

Выдает InvalidCastException, если oне является string. В противном случае, правопреемники oк s, даже если oесть null.

string s = o as string; // 2

Назначает nullв sслучае oне является stringили , если oесть null. По этой причине вы не можете использовать его с типами значений (в этом случае оператор никогда не сможет вернуться null). В противном случае, назначает oна s.

string s = o.ToString(); // 3

Вызывает исключение NullReferenceException, если oесть null. Назначает то, что o.ToString()возвращается s, независимо от типа o.


Используйте 1 для большинства конверсий - это просто и понятно. Я почти никогда не использую 2, так как если что-то не подходит, я обычно ожидаю исключения. Я видел только необходимость в этом типе возврата-нулевого функционала с плохо спроектированными библиотеками, которые используют коды ошибок (например, возвращают null = error вместо использования исключений).

3 не является приведением и является просто вызовом метода. Используйте его, когда вам нужно строковое представление нестрокового объекта.


2
Вы можете присвоить 'null' значениям-типам, когда они явно определены, например: int? я; строка s = "5"; i = s как int; // теперь у меня 5 s = null; i = s как int; // я теперь нулевой
Anheledir

3
RE: Anheledir На самом деле я был бы нулевым после первого звонка. Вы должны использовать явную функцию преобразования, чтобы получить значение строки.
Гуванте

45
RE: Sander На самом деле есть еще одна очень хорошая причина для использования, поскольку она упрощает ваш проверочный код (проверяйте нулевой, а не проверяйте нулевой и правильный тип). Это полезно, поскольку большую часть времени вы предпочитаете выбрасывать одно исключение. Но это очень верно, что слепые как звонки плохие.
Гуванте

5
# 2 удобен для таких вещей, как методы Equals, когда вы не знаете тип ввода. В общем, да, 1 предпочтительнее. Хотя предпочтительнее этого, очевидно, будет использование системы типов для ограничения одним типом, когда вы ожидаете только один :)
Calum

6
# 2 также полезен, когда у вас есть код, который может сделать что-то конкретное для специализированного типа, но в противном случае ничего бы не сделал.
AnthonyWJones

349
  1. string s = (string)o;Используйте, когда что-то определенно должно быть другим.
  2. string s = o as string;Используйте, когда что-то может быть другим.
  3. string s = o.ToString(); Используйте, когда вам все равно, что это такое, но вы просто хотите использовать доступное строковое представление.

1
Я чувствую, что этот ответ звучит хорошо, но он может быть неточным.
J Рив

1
Мне нравятся первые два, но я бы добавил «и вы уверены, что он не нулевой» к третьему варианту.
Уксонит

2
Вы можете использовать Элвиса (?) в эти дни, чтобы не беспокоиться об этом: obj? .ToString ()
Quibblesome

@Quibblesome - отличный ответ, но мне пришлось остановиться, чтобы подумать о твоем опровержении! я буквально поражаюсь, что язык существует уже более 15 лет. Кажется, будто вчера мы все были "нервными", пытаясь убедить старших разработчиков перейти на C #.
Griswald_911

1
@Quibblesome хороший ответ: вы будете раздражены, если я добавлю, что 1/2/3, так что нет необходимости прокручивать до OP. Я с ТАК оценил бы старые ответы согласно голосам!
почему

29

Это действительно зависит от того, знаете ли вы oстроку и что вы хотите с ней делать. Если ваш комментарий означает, что на oсамом деле это строка, я бы предпочел прямой(string)o акт - вряд ли она потерпит неудачу.

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

С asоператором, если oэто не строка, sустанавливается значение null, что удобно, если вы не уверены и хотите проверить s:

string s = o as string;
if ( s == null )
{
    // well that's not good!
    gotoPlanB();
}

Однако, если вы не выполните этот тест, вы будете использовать его sпозже и получите исключение NullReferenceException . Они, как правило, встречаются чаще, и их гораздо сложнее отследить, когда они происходят в дикой природе, поскольку почти каждая строка разыменовывает переменную и может ее выбросить. С другой стороны, если вы пытаетесь привести к типу значения (любому примитиву или структурам, таким как DateTime ), вы должны использовать прямое приведение -as не сработает.

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


2
Одно замечание - вы можете использовать asс типами значений Nullable . IE o as DateTimeне будет работать, но o as DateTime?будет ...
Джон Гибб

Почему бы не использовать if (s is string)вместо этого?
BornToCode

1
@BornToCode, для меня, во многом личное предпочтение. В зависимости от того, что вы делаете, часто после того, isкак вы сделаете это, вам все равно придется снова разыграть, так что у вас есть выбор, а затем - жесткий. По какой-то причине asпроверка « и ноль» показалась мне лучше.
Блэр Конрад

9

Если вы уже знаете, к какому типу он может быть приведен, используйте приведение в стиле C:

var o = (string) iKnowThisIsAString; 

Обратите внимание, что только с приведением в стиле C вы можете выполнять явное приведение типов.

Если вы не знаете, является ли это желаемый тип, и вы собираетесь использовать его, если это так, используйте в качестве ключевого слова:

var s = o as string;
if (s != null) return s.Replace("_","-");

//or for early return:
if (s==null) return;

Обратите внимание, что as не будет вызывать операторы преобразования типов. Он будет ненулевым, если объект не является нулевым и изначально имеет указанный тип.

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


3
Это интересный небольшой вопрос, касающийся операторов преобразования типов. У меня есть несколько типов, для которых я создал конверсии, поэтому должен следить за этим.
AnthonyWJones

7

Ключевое слово as хорошо в asp.net, когда вы используете метод FindControl.

Hyperlink link = this.FindControl("linkid") as Hyperlink;
if (link != null)
{
     ...
}

Это означает, что вы можете оперировать с типизированной переменной, вместо того, чтобы потом objectприводить ее, как если бы вы использовали прямое приведение:

object linkObj = this.FindControl("linkid");
if (link != null)
{
     Hyperlink link = (Hyperlink)linkObj;
}

Это не большая вещь, но она экономит строки кода и присваивания переменных, плюс это более читабельно


6

'as' основан на 'is', который является ключевым словом, которое проверяет во время выполнения, является ли объект полиморфно-совместимым (в основном, если приведение может быть выполнено) и возвращает нуль, если проверка не удалась.

Эти два эквивалентны:

Используя «как»:

string s = o as string;

Использование 'is':

if(o is string) 
    s = o;
else
    s = null;

Напротив, приведение в стиле c выполняется также во время выполнения, но выдает исключение, если приведение не может быть выполнено.

Просто чтобы добавить важный факт:

Ключевое слово «as» работает только со ссылочными типами. Вы не можете сделать:

// I swear i is an int
int number = i as int;

В этих случаях вы должны использовать кастинг.


Спасибо за указание на мою ошибку, вы правы. Я отредактировал ответ. ой, извини.
Серхио Акоста

5

2 полезно для приведения к производному типу.

Пусть животное:

b = a as Badger;
c = a as Cow;

if (b != null)
   b.EatSnails();
else if (c != null)
   c.EatGrass();

получит подается с минимальным слепков.


2
@Chirs Moutray, это не всегда возможно, особенно если это библиотека.
замедленная

5

Согласно экспериментам на этой странице: http://www.dotnetguru2.org/sebastienros/index.php/2006/02/24/cast_vs_as

(на этой странице иногда появляются ошибки "недопустимого реферера", поэтому просто обновите, если это так)

Вывод: оператор as обычно быстрее, чем приведение. Иногда во много раз быстрее, иногда чуть быстрее.

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

Так как он быстрее и «безопаснее» (не исключает исключения) и, возможно, легче читается, я рекомендую использовать «как» все время.


4

«(string) o» приведет к исключению InvalidCastException, поскольку прямого преобразования нет.

«o as string» приведет к тому, что s будет пустой ссылкой, а не будет выброшено исключение.

«o.ToString ()» сам по себе не является приведением типа, это метод, который реализуется объектом и, таким образом, так или иначе, каждым классом в .net, который «что-то делает» с экземпляром класс, к которому он обращен и возвращает строку.

Не забывайте, что для преобразования в строку также существует Convert.ToString (someType instanceOfThatType), где someType является одним из набора типов, по сути базовых типов каркасов.


3

Все приведенные ответы хороши, если бы я мог добавить что-то: чтобы напрямую использовать строковые методы и свойства (например, ToLower), вы не можете написать:

(string)o.ToLower(); // won't compile

Вы можете только написать:

((string)o).ToLower();

но вы могли бы написать вместо этого:

(o as string).ToLower();

asВариант более читаемый (по крайней мере , на мой взгляд).


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

@james - Но кто сказал, что единственная цель оператора as - генерировать исключение в случае сбоя приведения? Если вы знаете, что o является строкой и просто хотите написать более понятный код, вы можете использовать (o as string).ToLower()вместо нескольких запутанных скобок.
BornToCode

цель as как раз наоборот - она ​​не должна выдавать исключение при сбое приведения, она должна возвращать ноль. Допустим, ваш o является строкой со значением null, что будет потом? Подсказка - ваш вызов ToLower не удастся.
Джеймс

@james - Вы правы, но как насчет случаев, когда я точно знаю, что он не будет нулевым, и мне просто нужно выполнить приведение к компилятору, чтобы я мог получить доступ к методам этого объекта?
BornToCode

1
Вы определенно можете сделать это, но это не совсем лучший метод, потому что вы не хотите полагаться на вызывающую или внешние системы, чтобы гарантировать, что ваше значение не равно нулю. Если вы используете C # 6, тогда вы можете сделать (o как строка) ?. Понижать().
Джеймс

3
string s = o as string; // 2

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


Привет, Крис, ссылка, которая была в этом ответе, теперь 404 ... Я не уверен, что у тебя есть замена, которую ты хочешь поставить вместо нее?
Мэтт

3

Кажется, что они оба концептуально различны.

Прямой Кастинг

Типы не должны быть строго связаны. Это входит во все типы ароматов.

  • Пользовательское неявное / явное приведение: обычно создается новый объект.
  • Тип значения Неявный: Копирование без потери информации.
  • Тип значения Явный: Копирование и информация могут быть потеряны.
  • Отношение IS-A: Изменить тип ссылки, в противном случае выдается исключение.
  • Тот же тип: «Кастинг избыточен».

Такое ощущение, что объект собирается быть преобразован во что-то еще.

AS оператор

Типы имеют прямое отношение. Как в:

  • Типы ссылок: отношения IS-A Объекты всегда одинаковы, только ссылки меняются.
  • Типы значений: Копировать бокс и обнуляемые типы.

Такое ощущение, что вы собираетесь обращаться с объектом по-другому.

Образцы и ИЛ

    class TypeA
    {
        public int value;
    }

    class TypeB
    {
        public int number;

        public static explicit operator TypeB(TypeA v)
        {
            return new TypeB() { number = v.value };
        }
    }

    class TypeC : TypeB { }
    interface IFoo { }
    class TypeD : TypeA, IFoo { }

    void Run()
    {
        TypeA customTypeA = new TypeD() { value = 10 };
        long longValue = long.MaxValue;
        int intValue = int.MaxValue;

        // Casting 
        TypeB typeB = (TypeB)customTypeA; // custom explicit casting -- IL:  call class ConsoleApp1.Program/TypeB ConsoleApp1.Program/TypeB::op_Explicit(class ConsoleApp1.Program/TypeA)
        IFoo foo = (IFoo)customTypeA; // is-a reference -- IL: castclass  ConsoleApp1.Program/IFoo

        int loseValue = (int)longValue; // explicit -- IL: conv.i4
        long dontLose = intValue; // implict -- IL: conv.i8

        // AS 
        int? wraps = intValue as int?; // nullable wrapper -- IL:  call instance void valuetype [System.Runtime]System.Nullable`1<int32>::.ctor(!0)
        object o1 = intValue as object; // box -- IL: box [System.Runtime]System.Int32
        TypeD d1 = customTypeA as TypeD; // reference conversion -- IL: isinst ConsoleApp1.Program/TypeD
        IFoo f1 = customTypeA as IFoo; // reference conversion -- IL: isinst ConsoleApp1.Program/IFoo

        //TypeC d = customTypeA as TypeC; // wouldn't compile
    }

2

Я хотел бы обратить внимание на следующие особенности оператора as :

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/as

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


0

При попытке получить строковое представление чего-либо (любого типа), которое потенциально может быть нулевым, я предпочитаю приведенную ниже строку кода. Он компактен, вызывает ToString () и правильно обрабатывает нули. Если o равно null, s будет содержать String.Empty.

String s = String.Concat(o);

0

Поскольку никто не упомянул об этом, наиболее близким к instanceOf для Java по ключевому слову является следующее:

obj.GetType().IsInstanceOfType(otherObj)

0

Используйте прямое приведение, string s = (string) o;если в логическом контексте вашего приложения stringединственный допустимый тип. При таком подходе вы получите InvalidCastExceptionи внедрите принцип Fail-fast . Ваша логика будет защищена от дальнейшей передачи недопустимого типа или получения исключения NullReferenceException, если используется asоператор.

Если логика ожидает несколько различных типов приведения string s = o as string;и проверьте ее nullили используйтеis оператор.

В C # 7.0 появилась новая интересная функция для упрощения приведения и проверки соответствия шаблону :

if(o is string s)
{
  // Use string variable s
}

or

switch (o)
{
  case int i:
     // Use int variable i
     break;
  case string s:
     // Use string variable s
     break;
 }

0

Следующие две формы преобразования типов (приведение) поддерживаются в C #:

|

(Резюме

• Преобразовать статический тип v в c в данном выражении

• Возможно, только если динамический тип v равен c или подтип c

• Если нет, генерируется исключение InvalidCastException

|

V как C

• Не смертельный вариант (с) v

• Таким образом, преобразуйте статический тип v в c в данном выражении

• Возвращает ноль, если динамический тип v не c или подтип c

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.