2014-01-07 3 views
2

Например, у меня есть два класса, выполняющих ту же функцию:Как определить общий метод, возвращающий экземпляр типа класса получения?

class HDD : Disk 
{ 
    public HDD RMA() 
    { 
     HDD newHDD = RMAService.Exchange(this); 
     return newHDD; 
    } 
} 
class SSD : Disk 
{ 
    public SSD RMA() 
    { 
     SSD newSSD = RMAService.Exchange(this); 
     return newSSD; 
    } 
} 

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


То, что я пытался до сих пор:

(см решений ниже)

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

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

+1

Если бы я мог использовать оправдание «Мне трудно понять, как использовать тип» в моей дневной работе :( –

+0

Это может быть улучшено, если вы представите, что делает функция «Exchange». он должен быть разделен - для того, чтобы неизвестная часть типа оставалась в базовом классе, и строго типизированная часть была подклассифицирована или, возможно, что-то похожее на «Clone» может быть введено. Трудно сказать, не зная, что внутри 'Exchange'. – BartoszKP

+0

@ BartoszKP 'Exchange' - это любая функция, которая возвращает новый объект того же типа времени выполнения, что и переданный. Если вы хотите что-то сделать, просто напишите функцию, которая возвращает именно тот объект, который был передан.' Public T Exchange (T in) {return in;} ' – Alain

ответ

1

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

public abstract class Disk 
{ 
    public Disk() { } 
} 
public static class Disk_Extension_Methods 
{ 
    public static T RMA<T>(this T oldDisk) where T : Disk 
    { 
     T newDisk = RMAService.Exchange(oldDisk) 
     return newDisk; 
    } 
} 

Что позволяет идти:

public class HDD : Disk { } 
public class SSD : Disk { } 

HDD newHDD = someHDD.RMA(); 
+0

Это не скомпилируется :) – BartoszKP

+0

@BartoszKP - Забыл «статичные» модификаторы, когда печатал это бесплатно, но он работает для меня. – Alain

+0

Действительно, выглядит красиво :) – BartoszKP

1

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

abstract class Disk<T> where T : Disk<T> //Ugly recursive type parameter 
{ 
    public Disk() 
    { 
     if(this.GetType() != typeof(T)) //Ugly type check 
      throw new Exception("The type argument must be precisely the " + 
           "derived class you're defining. Annoying eh?") 
    } 

    public T RMA() 
    { 
     Disk<T> newDisk = RMAService.Exchange(this) 
     return (T)newDisk; //Ugly explicit cast 
    } 
} 

Что позволяет идти:

class HDD : Disk<HDD> { } //Ugly self-referencing type parameter 
class SSD : Disk<SSD> { } 

HDD newHDD = someHDD.RMA(); 
+0

Я предпочитаю это решение для вашего нового решения, потому что он поддерживает логику, связанную с объектом, в том же классе, что и объект. Хотя мне нравятся методы расширения, в этом случае они, похоже, добавляют другое место для поиска метода. – Magus

+0

@Magus - Это действительно вина Microsoft, не позволяющая статическим методам расширения быть определенными в нестатических или общих или даже вложенных классах. С точки зрения API (и Intellisense) невозможно отличить этот метод от тех, которые определены непосредственно в самих классах. – Alain

0

Не уверен, если это то, что вы хотите, но попробуйте отделить (скрытые) реализации в базовом классе из публичных методов интерфейса в производных подклассов ,

abstract class Disk //base class 
{ 
    protected Disk CreateRMA() 
    { 
     Disk newDisk; 
     newDisk = new RMAService.Exchange(this); 
     ...whatever else needs to be done... 
     return newDisk; 
    } 
} 

class HDD: Disk 
{ 
    public HDD RMA() 
    { 
     return CreateRMA(); 
    } 
} 

class SSD: Disk 
{ 
    public SSD RMA() 
    { 
     return CreateRMA(); 
    } 
} 

RMA() Метод каждого производного класса просто вызывает метод CreateRMA() базового класса, а затем отбрасывает полученный Disk объект производного типа класса. Каждое возвращаемое значение имеет соответствующий подтип, и вся фактическая работа для каждого метода RMA() выполняется в методе базового класса.

Теперь вы можете сделать это:

HDD disk1 = someHDD.RMA(); 
SSD disk2 = someSSD.RMA(); 

Добавления

Это не решает проблему Ора, так как вызов RMAService.Exchange() должен быть специфическим для (перегружено) каждого производного подтипа.Другой подход сохраняет этот вызов в методе производных типов, но позволяет базовый класс делать все остальное в любой требуется инициализация работы:

// Second approach 
abstract class Disk //base class 
{ 
    protected Disk CreateRMA(Disk newDisk) 
    { 
     ...other initialization code... 
     return newDisk; 
    } 

    public abstract Disk RMA(); //not sure if this works 
} 

class HDD: Disk 
{ 
    public override HDD RMA() 
    { 
     return CreateRMA(new RMAService.Exchange(this)); 
    } 
} 

class SSD: Disk 
{ 
    public override SSD RMA() 
    { 
     return CreateRMA(new RMAService.Exchange(this)); 
    } 
} 

Это немного больше работы для производных классов, но только для перегруженного вызова new; все остальные коды инициализации сохраняются в методе базового класса.

Surrender

Хорошо, это не будет работать, потому что C# не поддерживает covariant return types.

+1

. Я боюсь, что 'Exchange (this)' должен быть разрешен во время компиляции, поэтому он будет искать 'Exchange (Disk) ', а не' Exchange (HDD) 'или' Exchange (SSD) '. – MarcinJuraszek

+0

Я этого боялся. Тогда вам, возможно, придется использовать дженерики, как и другие. –

+1

Другая проблема заключается в том, что он не заставляет производные типы внедрять метод «RMA», поэтому, если кто-то пытался подклассифицировать «Диск», он не обязательно должен иметь метод RMA. У «Диска» не было бы метода «RMA» – psubsee2003

1

Вы можете сделать свой базовое решение немного более легким для чтения и избежать проверки типов в Disk конструктора с помощью protected конструктор и dynamic на Exchange вызова:

abstract class Disk<T> where T : Disk<T> { 
    protected Disk() 
    { 

    } 

    public T RMA() 
    { 
     return RMAService.Exchange((dynamic)this); 
    } 
} 

protected конструктор делает такие классы, как class HDD : Disk<SSD> неудачу во время компиляции и dynamic задерживает Exchange метод перегрузки, сопоставляющий решение до времени выполнения, поэтому вы получите правильный (или ошибку, если не подходит реальный тип this).

+0

Интересно. Я не знал, что ссылка на базовый тип на '(dynamic)' приведет к созданию правильной перегрузки во время выполнения. Это заменит случаи, когда вам в противном случае нужно пойти 'typeof (RMAService) .GetMethod (« Exchange »). MakeGenericMethod (new Type [] {this.GetType()}). Invoke (null, new object [] {this}); – Alain

+0

Да, в значительной степени да. – MarcinJuraszek

+0

Мой бог ... Столько уродливого кода нужно исправлять как можно скорее. – Alain

Смежные вопросы