Определение неявных и явных приведений для интерфейсов C # - PullRequest
21 голосов
/ 05 мая 2010

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

 public class Game
 {
     public class VariantInfo
     {
         public string Language { get; set; }
         public string Variant { get; set; }
     }
 }

А в ScrDictionary.cs мы имеем ...

 public class ScrDictionary: IScrDictionary
 {
     public string Language { get; set; }
     public string Variant { get; set; }

     public static implicit operator Game.VariantInfo(ScrDictionary s)
     {
        return new Game.VariantInfo{Language=sd.Language, Variant=sd.Variant};
     }
 }

И интерфейс ...

 public interface IScrDictionary
 {
     string Language { get; set; }
     string Variant { get; set; }
 }

Я хочу иметь возможность использовать IScrDictionary вместо ScrDictionary, но все же иметь возможность неявно преобразовывать ScrDictionary в Game.VariantInfo. Кроме того, хотя может быть простой способ сделать это, предоставив IScrDictionary свойство типа Game.VariantInfo, мой вопрос более общий: Есть ли способ определить приведение или перегрузку операторов на интерфейсах? (Если нет, то как правильно использовать C # для поддержки этой функциональности, не отказываясь от интерфейсно-ориентированного дизайна?)

Ответы [ 2 ]

7 голосов
/ 05 мая 2010

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

Вы все еще можете наследовать от абстрактного базового класса, который реализует интерфейс и предоставляет логику, необходимую для приведений или перегрузки операторов. Это не нарушает дизайн интерфейса. Классы, которые не наследуют от общего базового класса, но реализуют интерфейс, все равно должны будут независимо реализовывать свои собственные неявные приведения и перегрузки операторов. Если вы хотите централизовать логику для работы с классами, которые обычно реализуют интерфейс, вы можете сделать это в C # 3.0 + /. NET Fx 3.5 с методами расширения (или в предыдущих версиях со статическими методами). Ниже я продемонстрирую это с помощью служебного класса и двух классов, Foo и Bar, которые не имеют общего предка. Они разделяют код, который включает в себя служебную функцию Add, поэтому вам не нужно повторять эту реализацию в обоих классах.

public interface IInterface
{
    int X { get; set; }
    int Y { get; set; }
}

public static class IInterfaceTHelper
{
    public static IInterface Add<T>(this IInterface a, IInterface b) 
        where T : new()
    {
        var ret = (IInterface)new T();
        ret.X = a.X + b.X;
        ret.Y = a.Y + b.Y;
        return ret;
    }
}

class Foo : IInterface
{
    public int X { get; set; }
    public int Y { get; set; }

    public static IInterface operator +(Foo a, IInterface b)
    {
        return a.Add<Foo>(b);
    }
}

class Bar : IInterface
{
    public int X { get; set; }
    public int Y { get; set; }

    public static IInterface operator +(Bar a, IInterface b)
    {
        return a.Add<Bar>(b);
    }
}

class Program
{
    static void Main(string[] args)
    {
        var foo = new Foo { X = 5, Y = 3 };
        var bar = new Bar { X = 3, Y = 5 };

        var result = foo + bar;
        Console.WriteLine(result.GetType().Name + " " + result.X + " " + result.Y);
        result = bar + foo;
        Console.WriteLine(result.GetType().Name + " " + result.X + " " + result.Y);

        Console.ReadLine();
    }
}

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

4 голосов
/ 17 февраля 2011

Один из способов сделать это, если вам часто требуется приведение / преобразование, это определить явный метод в вашем интерфейсе, например,

public interface ISomeInterface
{
   TargetType ToTargetType();
}

Затем в абстрактном базовом классе вы можете определить неявное / явное приведение, и оператор приведения просто вызовет интерфейсный метод, в котором вы определяете действительную логику преобразования, например,

public abstract class SomeAbstractClass : ISomeInterface
{
  public TargetType ToTargetType()
  {
    // Actual cast logic goes here
    return (TargetType)this;
  }

  public static explicit operator TargetType(SomeAbstractClass obj)
  {
    return ToTargetType();
  }
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...