C # Объектно-ориентированный тип возврата «this» при дочернем вызове - PullRequest
4 голосов
/ 29 октября 2011

У меня есть 2 класса, каждый возвращает себя во всех функциях:

public class Parent{
   public Parent SetId(string id){
      ...
      return this
   }
}

public class Child : Parent{
   public Child SetName(string id){
      ...
      return this
   }
}

Я хочу включить этот вид API:

new Child().SetId("id").SetName("name");

SetName недоступенпоскольку SetId возвращает Parent, а SetName находится на Child.

Как?

Ответы [ 7 ]

8 голосов
/ 29 октября 2011

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

public abstract class Parent<T> where T : Parent<T>
{
    public T SetId(string id) {
        return (T)this;
    }
}

public class Child : Parent<Child>
{
    public Child SetName(string id) {
        return this;
    }
}

Теперь можно написать:

new Child().SetId("id").SetName("name");
3 голосов
/ 29 октября 2011

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

var child = new Child()
{
    Id = value1;
    Name = value2;
}

Вы также можете вызвать метод вместо установки значения внутри инициализатора.

1 голос
/ 29 октября 2011

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

Если я не пропустил что-то в вашей модели, кажется, что правильно настроенная схема инициализации, некоторые скучные определения свойств и, возможно, класс фабрики могут быть больше похожи на то, что вы ищете. Также кажется, что вы МОЖЕТЕ подумать о том, чтобы установить своего Родителя с ID как неизменяемое свойство, хотя ваши требования могут создать истинную причину для этого.

Вот некоторые потенциально полезные определения класса. Здесь, в Сент-Луисе, рано, и я еще не нахожусь в должном порядке, так что я могу что-то упустить. , , ; -)

Родитель:

public class Parent
{

    protected string _id;

    // Protected default constructor so tht this class can be inherited:
    protected Parent() { }

    // REquired to set ID at initialization; 
    public Parent(String ID)
    {
        _id = ID;
    }

    // Read-only:
    public String ID
    {
        get { return _id; }
    }


    // This seems a little dodgy. Why would you want to return a reference
    // to the current instance as a result of resetting the ID?
    public Parent SetID(String ID)
    {
        _id = ID;
        return this;
    }
}

Фабрика родителей:

public class ParentFactory
{
    public static Parent NewParent(String ID)
    {
        Parent newParent = new Parent(ID);
        return newParent;
    }
}

Ребенок:

public class Child : Parent
{

    private string _name;

    // Protected default constructor so tht this class can be inherited:
    protected Child() { }

    // Initialize a new Child with the ID property required for the parent:
    public Child(String ID)
    {
        base.SetID(ID);
    }


    // Initialize a new child with both properties set:
    public Child(String ID, String Name) : this(ID)
    {
        _name = Name;
    }

    // This could be read-only as well:
    public String Name
    {
        get { return _name; }
        set { _name = value; }
    }


    // Again, seems kinda dodgy: 
    public Child SetName(String name)
    {
        return this;
    }
}

Детская фабрика:

public class ChildFactory
{
    public static Child  NewChild(String ID)
    {
        Child newChild = new Child(ID);
        return newChild;
    }


    public static Child  NewChild(String ID, String Name)
    {
        Child newChild = new Child(ID, Name);
        return newChild;
    }
}
0 голосов
/ 30 октября 2011

Что не так с этим: ((Child) (new Child (). SetId ("id")). SetName ("name");

Объяснение здесь состоит в том, что, хотя типом времени выполнения объекта является Child, вывод статического типа C #, который используется при компиляции выражения, которое вы хотите поддерживать, заключает, что статический тип SetId ("id") является Parent. И это так, поскольку SetId определен в родительском классе.

Фактически ваш объект имеет тип Child и имеет метод SetName. Таким образом, SetName доступен вам, но только если вы скажете C # рассматривать объект как дочерний элемент. В вашем выражении вы этого не сделаете, но, поскольку у Parent нет метода SetName, ваш код не будет проверять тип, поэтому он не будет компилироваться.

Если вы выполняете приведение C #, на всякий случай проверит тип, но затем выполнит вызов, который вы имели в виду.

0 голосов
/ 29 октября 2011

Я нашел похожий вопрос, но для языка Java здесь: Способ заставить метод родительского класса Java возвращать объект дочернего класса

пытался скомпилировать его, но не работает, посмотрите мой отрывок и посмотрите мой комментарий, в основном похоже на то, что в Java мы можем привести это к T, но теперь в C #. интересно ...

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

class Program
{
    static void Main(string[] args)
    {
        var obj = new Child().SetId("id").SetName("aaa");
    }
}

public class Parent<T>
{
    public virtual T SetId(string id)
    {
        // this does not work
        return (T)this;

        // this compiles but is NOT what we want :(
        //return default(T);
    }
}

public class Child : Parent<Child>
{
    public Child SetName(string name)
    {
        return this;
    }
}
0 голосов
/ 29 октября 2011

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

Как насчет использования простых старых свойств для 'Name' и 'Id'?Это удобно и удобно, особенно при использовании синтаксиса Collection Initialiser .

0 голосов
/ 29 октября 2011

Давиде Пирас прав, способ сделать это - использовать виртуальный метод или лучше интерфейс.

См. Этот код

public interface INode
{
    INode SetName(string aName);
    INode SetId(string aId);
}

public class Parent : INode
{
    public virtual INode SetId(string id)
    {
        ...
        return this
    }

    public virtual INode SetName(string aId)
    {
        return this;
    }
}

public class Child : Parent
{
   public override INode SetName(string id)
   {
      ...
      return this;
   }
}
...