Сможет ли .NET функционировать так же хорошо без использования типа Object? - PullRequest
7 голосов
/ 08 сентября 2010

Я спрашиваю об этом, потому что кажется, что использование Object кажется простым способом решения определенных проблем, таких как «У меня нет определенного типа, поэтому используйте Object» и т. Д.

Кроме того, причина, по которой меня это заинтересовало, заключается в том, что мой коллега сказал мне, что если бы .NET была истинно объектно-ориентированной платформой, то ей не пришлось бы использовать весь тип перехвата типа Object.

Так что, если бы в .NET не было типа Object, каковы были бы альтернативные способы решения возникающих проблем, чтобы он функционировал точно так же?

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

РЕДАКТИРОВАТЬ: еще одно замечание, которое я запомнил, потому что тип объекта существует, его эффект распространяется по всему .NET. Как IEnumerable существует, но также IEnumerable . И во многих ситуациях вам приходится реализовывать как общие, так и неуниверсальные версии вещей и т. Д.

Ответы [ 9 ]

3 голосов
/ 08 сентября 2010

Это удобство, не только для использования в качестве «универсального» типа, но также для отражения, сбора мусора и т. Д.

Другие языки (C ++) могут обходиться без, но я бы не решился сказать, что это делаетэти языки больше ООП.

3 голосов
/ 08 сентября 2010

Ваш друг, вероятно, работает с динамическим языком (например, ruby ​​или python), верно?

Существует очень много людей, которые считают, что строго типизированные языки следует называть «ориентированными на класс», а не «объектно-ориентированными», потому что все возможные варианты поведения и полиморфизм должны быть заранее определены в определении класса. Когда ваш метод принимает что-либо, любые проверки выполняются на основе возможностей объекта, а не его класса, вы можете сказать, что это более объектно-ориентированный подход.

Я как бы противоречу этому аргументу. В кругах программистов существует тупое убеждение, что ОО однозначно хорошо, независимо от того, в чем заключается проблема или требования. Из-за этого люди, как правило, пытаются выиграть аргументы, говоря: «то и то не является объектно-ориентированным», а поскольку объектно-ориентированное является синонимом добра, они побеждают. Несмотря на то, что я считаю, что работать со статическими языками - боль, я считаю, что называть их не ОО - неискренний способ выразить свою точку зрения. С другой стороны, все, что заставит программиста выйти за пределы своей зоны комфорта и научиться новому способу что-то делать (если по какой-то другой причине выиграть спор) не может быть абсолютно плохим. Как я уже сказал, конфликтует. :)

3 голосов
/ 08 сентября 2010

Я бы сказал, что проблема, решаемая Object, заключается не в том, что «у меня нет определенного типа, поэтому используйте Object», а в том, что «мне все равно, какой это тип; все, что мне нужно»знать это, что это Object "

2 голосов
/ 08 сентября 2010

Помните, что наследование обозначает отношения "есть". Каждый класс в .NET является (n) объектом. У всех них есть ToString метод. Все они имеют тип, к которому вы получаете доступ через GetType. Такого рода отношения и получающееся в результате совместное использование функциональности являются основой объектно-ориентированного программирования.

2 голосов
/ 08 сентября 2010

В строго типизированном фреймворке Объекты должны где-то начинаться.

Тип object не предназначен для предоставления "простого" универсального варианта приведения илизаставить вас низвергнуть до наименьшего общего знаменателя.Он предназначен для обеспечения абсолютного наиболее общего случая для объекта - основы для сравнения ссылок и удобного ToString() метода, среди нескольких других.

1 голос
/ 09 сентября 2010

Идея о том, что понятие System.Object может быть заменено интерфейсами, которые, как правило, реализуют все классы, неоднократно упоминалась в этом вопросе.Хотя я думаю, что идея верна, в конце я бы сказал, что она вам ничего не купит.Даже если бы такие интерфейсы существовали, все равно оставался бы вопрос о том, как компилятор на самом деле будет гарантировать, что классы реализуют эти интерфейсы.

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

// let's start off by defining interfaces to describe the various methods that are currently available from the System.Object class

public interface IEquatable
{
    bool Equals(IEquatable other);
}

public interface IHashCodeGenerator
{
    int GetHashCode();
}

public interface ITypeIdentifiable
{
    Type GetType();
}

public interface IConvertibleToString
{
    string ToString();
}

// This guy throws a wrench into things, because we can't privately (or "protectedly") implement an interface.
// This is discussed further below on the MyClass.MemberwiseClone method.
public interface IMemberwiseCloneable
{
}

// This class simply encapsulates similar functionality found within the System.Object class
public static class ClrInternals
{
    [MethodImpl(MethodImplOptions.InternalCall)]
    internal static extern bool Equals(IEquatable objA, IEquatable objB);

    [MethodImpl(MethodImplOptions.InternalCall)]
    internal static extern int GetHashCode(IHashCodeGenerator hashGenerator);

    [MethodImpl(MethodImplOptions.InternalCall)]
    internal static extern Type GetType(ITypeIdentifiable typedInstance);

    [MethodImpl(MethodImplOptions.InternalCall)]
    internal static extern IMemberwiseCloneable MemberwiseClone(IMemberwiseCloneable original);
}

// let's say that as a rule the compiler implicitly makes all classes implement these interfaces
class MyClassExampleA : IEquatable, IHashCodeGenerator, ITypeIdentifiable, IConvertibleToString, IMemberwiseCloneable
{
    // The compiler also implicitly makes all classes implement the interfaces with the following code (unless otherwise specified)

    #region IEquatable Members

    public bool Equals(IEquatable other)
    {
        // let's suppose that this is equivalent to the current implementation of Object.Equals
        return ClrInternals.Equals(this, other);
    }

    #endregion

    #region IHashCodeGenerator Members

    public int GetHashCode()
    {
        // let's suppose that this is equivalent to the current implementation of Object.GetHashCode
        return ClrInternals.GetHashCode(this);
    }

    #endregion

    #region ITypeIdentifiable Members

    public Type GetType()
    {
        // let's suppose that this is equivalent to the current implementation of Object.GetType
        return ClrInternals.GetType(this);
    }

    #endregion

    #region IConvertibleToString Members

    public string ToString()
    {
        // let's suppose that this is equivalent to the current implementation of Object.ToString
        return this.GetType().ToString();
    }

    #endregion

    // this one is perhaps a little goofy, since it doesn't satisfy any interface
    // In order to be equivalent to the current Object.MemberwiseClone implementation, I've made this protected,
    // but we cannot have a protected method that implements an interface, so this throws a wrench into things.
    protected MyClassExampleA MemberwiseClone()
    {
        // let's suppose that this is equivalent ot the current implementation of Object.MemberwiseClone
        return (MyClassExampleA)ClrInternals.MemberwiseClone(this);
    }

    // ** All of the above code is just a representation of the implicit semantics that the compiler/CLR applies to a class.  Perhaps this code is not actually generated by the compiler for each class (that would be a lot of duplication!), but rather the CLR might handle this logic internally
}


// Ok, so now I'm implementing a general Stack class
public class Stack
{
    // what type should I use for the parameter?
    // I have five different interfaces to choose from that I know all classes implement, but which one should I pick?
    public void Push(type??? item)
    {
        // ...
    }

    // what type should I use for the return type?
    // I have five interfaces to choose from, but if I return one,
    // then my caller can't utilize the methods defined in the other interfaces without casting.
    // I know all classes implement all five interfaces, but is it possible that my Stack might also contain non-class objects that don't implement all interfaces?  In that case it might be dangerous for the caller to cast the return value from one interface to another.
    public type??? Pop()
    {
        // ...
    }

    // In C++ I could have used void* or defined the Stack class as a template
}

// moving on...
class StackUtilizer
{
    // here I try to utilize the Stack class
    public void UseStack(Stack stack)
    {
        // what type should I use for the variable to hold the result of the Stack.Pop method?
        type??? item = stack.Pop();

        // if I use IEquatable
        IEquatable item1 = stack.Pop();

        IEquatable item2 = stack.Pop();

        item1.Equals(item2); // then I can do this

        Type itemType = item1.GetType(); // but I can't do this

        string s = item1.ToString(); // nor can I do this

        // Ok, this calls for another interface that composes all of these other interfaces into one
    }
}


// let's define a single interface that pulls all of these other interfaces together
public interface IObject : IEquatable, IHashCodeGenerator, ITypeIdentifiable, IConvertibleToString, IMemberwiseCloneable
{
    // no need to define any methods on this interface.  The purpose of this interface is merely to consolidate all of these other basic interfaces together.
}

// now we change the compiler rule to say that all classes implicitly implement the IObject interface
class MyClassExampleB : IObject
{
    // ... <refer to MyClassExampleA for the implicit implementation of the interfaces>
}

// now let's try implementing that Stack class again
public class Stack
{
    // I know that all classes implement the IObject interface, so it is an acceptable type to use as a parameter
    public void Push(IObject item)
    {
        // ...
    }

    // again, since all classes implement IObject, I can use it as the return type
    public IObject Pop()
    {
        // ...
        throw new NotImplementedException("This is an example.  The implementation of this method is irrelevant.");
    }
}

class StackUtilizer
{
    // here I try to utilize the Stack class
    public void UseStack(Stack stack)
    {
        // now I can just use IObject for my variables holding the return value of the Stack.Pop method
        IObject item = stack.Pop();

        // if I use IObject
        IObject item1 = stack.Pop();

        IObject item2 = stack.Pop();

        item1.Equals(item2); // then I can do this

        Type itemType = item1.GetType(); // and I can do this

        string s = item1.ToString(); // and I can do this
    }
}

Итак, в конце концов, у нас все еще есть интерфейс IObject, аналогичный текущему классу System.Object.Открытый вопрос заключается в том, как компилятор / CLR будет выполнять наше правило, согласно которому все классы реализуют интерфейс IObject.Я могу думать о трех возможных подходах:

  1. Компилятор генерирует неявную реализацию интерфейса для каждого класса, что может вызвать многократное дублирование.
  2. CLR будет обрабатывать эти реализации интерфейса вособый способ, который не требовал бы, чтобы компилятор фактически генерировал код для каждого класса.
  3. Мы определяем базовый класс, назовем его Object (начинает звучать знакомо?), который реализует интерфейс IObjectи мы изменили правило, сказав, что все классы неявно наследуются от Object (это именно то, что мы имеем сегодня, но без интерфейсов).
1 голос
/ 08 сентября 2010

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

Давайте рассмотрим простой пример с классом ArrayList .NET Framework. Это было частью оригинальной структуры, до того, как были представлены Generics. Авторы класса ArrayList пытались предоставить полезную реализацию динамического списка, но у них не было возможности узнать, какие типы объектов будут вставлены в список. Они использовали тип Object для представления элементов в списке, потому что это позволило бы добавить в список любой тип класса. Например:

        ArrayList people = new ArrayList();
        people.Add(new Doctor());
        people.Add(new Lawyer());
        people.Add(new PetDetective());
        people.Add(new Ferrari()); // Yikes!

        // ...

        for (int i = 0; i < people.Count; i++)
        {
            object person = people[0];
            // ...
        }

Теперь, если это было ваше собственное приложение, и вы знали, что все ваши классы Doctor, Lawyer и PetDetective являются производными от общего базового класса Person, то теоретически вы можете создать свой собственный. реализация связанного списка на основе класса Person, а не класса Object. Тем не менее, это большая дополнительная работа для очень небольшой выгоды, когда у вас уже есть встроенный и протестированный класс ArrayList. Если вы действительно хотите сделать его специфичным для вашего базового класса Person, то вы всегда можете создать класс-оболочку для ArrayList, который принимает только объекты Person.

В C ++ вы можете сделать то же самое, используя тип данных «void pointer» (void*). Тем не менее, C ++ также поддерживает шаблоны (очень похожие на шаблоны), что значительно облегчает создание полезного компонента, не зная деталей, с какими другими классами он будет использоваться. Поскольку C # изначально не поддерживал дженерики, использование типа Object было действительно единственным способом создания общих полиморфных компонентов для использования другими людьми.

1 голос
/ 08 сентября 2010

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

Помимо этого, я подозреваю, что функциональность объекта могла бы обрабатываться примерно так же отдельными интерфейсами (IEqualable и IConvertableToString).С другой стороны, иногда полезны виртуальные методы объекта, особенно ToString, которые могут использоваться IDE или отладчиком при отображении состояния программы.Это действительно прагматичный дизайн.

1 голос
/ 08 сентября 2010

Да, тип объекта может быть использован не по назначению, но он предоставляет основную функциональность миру .NET (прежде всего IMO - это GetType ()).Так что, если есть проблема, дело не в том, чтобы иметь тип объекта.

Альтернатив много, включая общие методы и методы ООП, такие как интерфейсы ...

...