Подтвердить что ты не робот

Почему я не могу передать одно экземпляр родового типа другому?

Как я могу реализовать структуру, чтобы выполнить следующую трансляцию?

var a = new StatusedValue<double>(1, false);
var b = (StatusedValue<int>)a;

Моя реализация должна вести себя аналогично Nullable<T>, которая отлично работает. Однако этот код не работает с System.InvalidCastException:

public struct StatusedValue<T>  where T : struct
{
    public StatusedValue(T value) : this(value, true)
    {

    }

    public StatusedValue(T value, bool isValid)
    {
        this.value = value;
        this.isValid = isValid;
    }

    private T value;
    private bool isValid;

    public static implicit operator StatusedValue<T>(T value)
    {
        return new StatusedValue<T>(value);
    }

    public static explicit operator T(StatusedValue<T> value)
    {
        return value.value;
    }
}

Результат:

Невозможно передать объект типа "StatusedValue`1 [System.Double]" для ввода 'StatusedValue`1 [System.Int32]'.

4b9b3361

Ответ 1

Это работает для типов Nullable<T>, потому что они получают специальное лечение от компилятора. Это называется "отмененным оператором преобразования", и вы не можете определить свой собственный.

Из раздела 6.4.2 спецификация С#:

6.4.2 Поднятые операторы преобразования

С учетом пользовательского оператора преобразования, который преобразует из значения N с недействительным значением S в непустое значение типа T, существует оператор с отмененным преобразованием, который преобразует из S? к Т?. Этот оператор с отмененным преобразованием выполняет Развертывание с S? на S, за которым следует пользовательское преобразование из S к T, за которым следует обертка из T в T?, за исключением того, что нулевой S? преобразуется непосредственно к нулевому значению T?. Оператор с отмененным преобразованием имеет ту же неявную или явную классификацию, что и ее базовая пользовательский оператор преобразования. Термин "пользовательское преобразование" применяется к использованию как пользовательского, так и отмененного преобразования Операторы

Ответ 2

Если вы счастливы вызвать метод, попробуйте

public StatusedValue<U> CastValue<U>() where U : struct
{
    return new StatusedValue<U>((U)Convert.ChangeType(value, typeof(U)), isValid);
}

Это, к сожалению, будет вытесняться во время выполнения, а не компилировать время, если T не может быть преобразовано в U.

Изменить: Как указано ниже, если вы ограничиваете IConvertible, а также/вместо struct, то каждое преобразование теоретически возможно во время компиляции, и вы получите только из-за неправильных значений времени выполнения.

Ответ 3

Nullables специально обрабатываются компилятором, я не знаю, так ли это здесь.

Ваши операторы позволят это:

StatusedValue<int> b = (int)a;

который, вероятно, не то, что вы хотите, потому что IsValid не копируется таким образом.

Вы можете реализовать метод расширения следующим образом:

 public static StatusedValue<TTarget> Cast<TSource, TTarget>(this StatusedValue<TSource> source)
    where TTarget : struct
    where TSource : struct
  {
    return new StatusedValue<TTarget>(
      (TTarget)Convert.ChangeType(
        source.Value, 
        typeof(TTarget)),
      source.IsValid);
  }


  b = a.Cast<int>();

Но компилятор не может проверить, совместимы ли эти типы. ChangeType также возвращает объект, тем самым боксируя ваше значение.

Ответ 4

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

    public StatusedValue<int> ConvertToStatusedInt() {
        return new StatusedValue<int>(Convert.ToInt32(value), isValid);
    }

Как указано в комментарии:

    public StatusedValue<Q> ConvertTo<Q>() where Q:struct {
        return new StatusedValue<Q>((Q)Convert.ChangeType(value, typeof(Q)), isValid);
    }

Ответ 5

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

public StatusedValue<TResult> To<TResult>(Func<T, TResult> convertFunc)
where TResult : struct {
   return new StatusedValue<TResult>(convertFunc(value), isValid);
}

Затем вы можете:

var a = new StatusedValue<double>(1, false);
var b = a.To(Convert.ToInt32);

С некоторым отражением вы можете создать таблицу поиска методов Convert и найти правильный вариант на основе аргументов типа, а затем вы можете по умолчанию преобразовать функцию null, и если она не указана, попробуйте автоматически искать правильное преобразование. Это удалит неуклюжую часть Convert.ToInt32 и просто сделает var b = a.To<int>();

Как указывает Ролинг, Convert.ChangeType можно использовать. Это заставит мой метод выглядеть так:

public StatusedValue<T2> To<T2>(Func<T, T2> convertFunc = null)
where T2 : struct {
   return new StatusedValue<T2>(
      convertFunc == null
         ? (T2)Convert.ChangeType(value, typeof(T2))
         : convertFunc(value),
      isValid
   );
}

Ответ 6

Ответ на вопрос, почему это так уже был опубликован и помечен как ответ.

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

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

public static class StatusedValue
{
    public static StatusedValue<T> Create<T>(T value, bool isValid = true) where T: struct
    {
        return new StatusedValue<T>(value, isValid);
    }
}

Далее вам нужно выставить базовое значение с помощью свойства Value (иначе вы не можете его отличить от кода).

Наконец, вы можете изменить исходный код:

var a = new StatusedValue<double>(1, false);
var b = (StatusedValue<int>)a;

Для этого:

var a = StatusedValue.Create(1.0, false);
var b = StatusedValue.Create((int)a.Value, false); 

где вы делаете простой перевод на a.Value.