Пустое или сравнение по умолчанию универсального аргумента в C#

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

String[] phrases = new String[10];
String keyPhrase = "Bird";
for(String phrase : phrases) {
    System.out.println(phrase.equals(keyPhrase));
}

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

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

Вы должны инициализировать элементы в массиве перед доступом или разыменованием их.

String[] phrases = new String[] {"The bird", "A bird", "My bird", "Bird"};
String keyPhrase = "Bird";
for(String phrase : phrases) {
    System.out.println(phrase.equals(keyPhrase));
}

269
задан Tim B 9 December 2015 в 02:23
поделиться

10 ответов

Чтобы избежать боксов, лучший способ сравнить универсальные шаблоны на равенство - использовать EqualityComparer .Default . Это касается IEquatable (без бокса), а также object.Equals и обрабатывает все нюансы Nullable "приподнятые". Следовательно:

if(EqualityComparer<T>.Default.Equals(obj, default(T))) {
    return obj;
}

Это будет соответствовать:

  • null для классов
  • null (пусто) для Nullable
  • zero / false / etc для других структур
539
ответ дан 23 November 2019 в 02:19
поделиться

Как насчет этого:

if (object.Equals(myArgument, default(T)))
{
    //...
}

Используя static object.Equals() метод избегает потребности в Вас сделать эти null проверка самих. Явно квалификация вызова с object., вероятно, не необходима в зависимости от Вашего контекста, но меня обычно префикс static вызовы с именем типа только для создания кода более разрешимым.

117
ответ дан Tim Cooper 23 November 2019 в 02:19
поделиться

Я смог расположиться статья Microsoft Connect, которая обсуждает этот вопрос в некоторых деталях:

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

, Если типы, как известно, являются ссылочными типами, перегрузкой по умолчанию определенных на объектных тестовых переменных для ссылочного равенства, хотя тип может определить свою собственную перегрузку. Компилятор определяет, какую перегрузку использовать на основе статического типа переменной (определение не является полиморфным). Поэтому при изменении примера для ограничения универсального параметра типа T к неизолированному ссылочному типу (такому как Исключение), компилятор может определить определенную перегрузку для использования, и следующий код скомпилировал бы:

public class Test<T> where T : Exception

, Если типы, как известно, являются типами значения, выполняет определенные тесты равенства значения на основе точных используемых типов. Нет никакого хорошего сравнения "по умолчанию" здесь, так как ссылочные сравнения не значимы на типах значения, и компилятор не может знать который определенное сравнение значения испустить. Компилятор мог испустить вызов к ValueType. Равняется (Объект), но этот метод использует отражение и довольно неэффективен по сравнению с определенными сравнениями значения. Поэтому, даже если необходимо было определить ограничение типа значения на T, нет ничего разумного для компилятора для генерации здесь:

public class Test<T> where T : struct

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

Вот то, что можно сделать...

я проверил это оба из этих методов работа для универсального сравнения ссылки и оцениваю типы:

object.Equals(param, default(T))

или

EqualityComparer<T>.Default.Equals(param, default(T))

, Чтобы сделать сравнения с "==" оператор необходимо будет использовать один из этих методов:

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

public void MyMethod<T>(T myArgument) where T : MyBase

компилятор тогда распознает, как выполнить операции на MyBase и не бросит "Оператор '==', не может быть применен к операндам типа 'T' и 'T'" ошибка, которую Вы видите теперь.

Другая опция состояла бы в том, чтобы ограничить T любым типом, который реализует IComparable.

public void MyMethod<T>(T myArgument) where T : IComparable

И затем используют CompareTo метод, определенный интерфейс IComparable .

27
ответ дан increddibelly 23 November 2019 в 02:19
поделиться

Попробуйте это:

if (EqualityComparer<T>.Default.Equals(myArgument, default(T)))

, который должен скомпилировать и сделать то, что Вы хотите.

18
ответ дан angry person 23 November 2019 в 02:19
поделиться

Не знайте, работает ли это с Вашими требованиями или нет, но Вы могли бы вынудить T быть Типом, который реализует интерфейс, такой как IComparable, и затем используйте ComparesTo () метод от того интерфейса (какие поддержки/дескрипторы IIRC аннулирует) как это:

public void MyMethod<T>(T myArgument) where T : IComparable
...
if (0 == myArgument.ComparesTo(default(T)))

существуют, вероятно, другие интерфейсы, что Вы могли использовать также IEquitable, и т.д.

-1
ответ дан caryden 23 November 2019 в 02:19
поделиться

@ilitirit:

public class Class<T> where T : IComparable
{
    public T Value { get; set; }
    public void MyMethod(T val)
    {
        if (Value == val)
            return;
    }
}

Оператор '==' не может быть применен к операндам типа 'T' и 'T'

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

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

-2
ответ дан cfeduke 23 November 2019 в 02:19
поделиться

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

public static bool IsNullOrEmpty<T>(T value)
{
    if (IsNull(value))
    {
        return true;
    }
    if (value is string)
    {
        return string.IsNullOrEmpty(value as string);
    }
    return value.Equals(default(T));
}

public static bool IsNull<T>(T value)
{
    if (value is ValueType)
    {
        return false;
    }
    return null == (object)value;
}

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

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

Используя эти методы, ведет себя следующий код, как Вы могли бы ожидать:

class Program
{
    public class MyClass
    {
        public string MyString { get; set; }
    }

    static void Main()
    {
        int  i1 = 1;    Test("i1", i1); // False
        int  i2 = 0;    Test("i2", i2); // True
        int? i3 = 2;    Test("i3", i3); // False
        int? i4 = null; Test("i4", i4); // True

        Console.WriteLine();

        string s1 = "hello";      Test("s1", s1); // False
        string s2 = null;         Test("s2", s2); // True
        string s3 = string.Empty; Test("s3", s3); // True
        string s4 = "";           Test("s4", s4); // True

        Console.WriteLine();

        MyClass mc1 = new MyClass(); Test("mc1", mc1); // False
        MyClass mc2 = null;          Test("mc2", mc2); // True
    }

    public static void Test<T>(string fieldName, T field)
    {
        Console.WriteLine(fieldName + ": " + IsNullOrEmpty(field));
    }

    // public static bool IsNullOrEmpty<T>(T value) ...

    // public static bool IsNull<T>(T value) ...
}
1
ответ дан Damian Powell 23 November 2019 в 02:19
поделиться

(Edited)

Marc Gravell has the best answer, but I wanted to post a simple code snippet I worked up to demonstrate it. Just run this in a simple C# console app:

public static class TypeHelper<T>
{
    public static bool IsDefault(T val)
    {
         return EqualityComparer<T>.Default.Equals(obj,default(T));
    }
}

static void Main(string[] args)
{
    // value type
    Console.WriteLine(TypeHelper<int>.IsDefault(1)); //False
    Console.WriteLine(TypeHelper<int>.IsDefault(0)); // True

    // reference type
    Console.WriteLine(TypeHelper<string>.IsDefault("test")); //False
    Console.WriteLine(TypeHelper<string>.IsDefault(null)); //True //True

    Console.ReadKey();
}

One more thing: can someone with VS2008 try this as an extension method? I'm stuck with 2005 here and I'm curious to see if that would be allowed.


Edit: Here is how to get it working as an extension method:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // value type
        Console.WriteLine(1.IsDefault());
        Console.WriteLine(0.IsDefault());

        // reference type
        Console.WriteLine("test".IsDefault());
        // null must be cast to a type
        Console.WriteLine(((String)null).IsDefault());
    }
}

// The type cannot be generic
public static class TypeHelper
{
    // I made the method generic instead
    public static bool IsDefault<T>(this T val)
    {
        return EqualityComparer<T>.Default.Equals(val, default(T));
    }
}
7
ответ дан 23 November 2019 в 02:19
поделиться

Для обработки всех типы T, включая примитивный тип T, вам нужно будет скомпилировать оба метода сравнения:

    T Get<T>(Func<T> createObject)
    {
        T obj = createObject();
        if (obj == null || obj.Equals(default(T)))
            return obj;

        // .. do a bunch of stuff
        return obj;
    }
6
ответ дан 23 November 2019 в 02:19
поделиться

Здесь может возникнуть проблема -

Если вы собираетесь позволить этому работать для любого типа, по умолчанию (T) всегда будет null для ссылочных типов и 0 (или структура, полная 0) для типов значений.

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

В качестве альтернативы,

2
ответ дан 23 November 2019 в 02:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: