Могу ли я определить, был ли мне дан новый объект в качестве параметра? - PullRequest
8 голосов
/ 17 декабря 2009

Короткая версия

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

Есть ли способ применить политику "только новые объекты" или "только существующие объекты" для параметров метода ?

Длинная версия

Существует множество методов, которые принимают объекты в качестве параметров, и не имеет значения, имеет ли метод объект «все для себя» или нет. Например:

var people = new List<Person>();

Person bob = new Person("Bob");

people.Add(bob);
people.Add(new Person("Larry"));

Здесь метод List<Person>.Add взял «существующий» Person (Боб), а также «новый» Person (Ларри), и список содержит оба элемента. Боб может быть доступен как bob или people[0]. Доступ к Ларри можно получить как people[1], а затем, при желании, кешировать и получить к нему доступ как larry (или любой другой) после этого.

ОК, хорошо. Но иногда метод не должен передаваться новому объекту. Взять, к примеру, Array.Sort<T>. Следующее не имеет большого смысла:

Array.Sort<int>(new int[] {5, 6, 3, 7, 2, 1});

Весь приведенный выше код - это взять новый массив, отсортировать его и затем забыть о нем (поскольку счетчик ссылок достигает нуля после выхода из Array.Sort<int>, и поэтому отсортированный массив будет собираться мусором, если я не ошибаюсь) , Так что Array.Sort<T> ожидает «существующий» массив в качестве аргумента.

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

DataTable firstTable = myDataSet.Tables["FirstTable"];
DataTable secondTable = myDataSet.Tables["SecondTable"];

firstTable.Rows.Add(secondTable.Rows[0]);

Как я уже сказал, это не лучший пример, поскольку DataRowCollection.Add на самом деле не ожидает new DataRow, точно; но ожидает DataRow, который еще не принадлежит DataTable. Так что последняя строка в коде выше не будет работать; должно быть:

firstTable.ImportRow(secondTable.Rows[0]);

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

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


EDIT

Более длинная версия становится длиннее.

Хорошо, предположим, у меня есть какой-то метод, который я хочу опционально принять TextWriter для вывода его прогресса или что-у-вас:

static void TryDoSomething(TextWriter output) {
    // do something...
    if (output != null)
        output.WriteLine("Did something...");

    // do something else...
    if (output != null)
        output.WriteLine("Did something else...");

    // etc. etc.

    if (output != null)
        // do I call output.Close() or not?
}

static void TryDoSomething() {
    TryDoSomething(null);
}

Теперь давайте рассмотрим два разных способа, которыми я мог бы вызвать этот метод:

string path = GetFilePath();
using (StreamWriter writer = new StreamWriter(path)) {
    TryDoSomething(writer);

    // do more things with writer
}

OR

TryDoSomething(new StreamWriter(path));

Хм ... может показаться, что это создает проблему, не так ли? Я создал StreamWriter, который реализует IDisposable, но TryDoSomething не собирается предполагать, знает ли он эксклюзивный доступ к своему аргументу output или нет. Таким образом, объект либо преждевременно удаляется (в первом случае), либо вообще не удаляется (во втором случае).

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

Ответы [ 9 ]

10 голосов
/ 17 декабря 2009

Нет, в основном.

Там действительно нет никакой разницы между:

var x = new ...;
Foo(x);

и

Foo(new ...);

и действительно, иногда вы можете конвертировать между ними в целях отладки.

Обратите внимание, что в примере DataRow / DataTable существует альтернативный подход - DataRow может знать своего родителя как часть своего состояния. Это не то же самое, что быть «новым» или нет - у вас может быть, например, операция «отсоединить». Определение условий с точки зрения подлинного жесткого и быстрого состояния объекта имеет гораздо больший смысл, чем неясные термины, такие как «новый».

6 голосов
/ 17 декабря 2009

Да, есть способ сделать это.

Сортировка.

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

DoSomething(ref new Customer());

Если вы это сделаете, вы получите ошибку «Аргумент ref или out должен быть назначаемой переменной».

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

Я не говорю, что это хороший стиль, обязательно. Вы не должны использовать ref или out, если вам действительно не нужно и у вас нет другого способа сделать то, что вы делаете. Но использование ref сделает то, что вы хотите сделать.

5 голосов
/ 17 декабря 2009

Нет. И если для этого есть какая-то причина, ваш код имеет неправильную архитектуру.

3 голосов
/ 19 декабря 2009

Позвольте мне переписать ваш вопрос на что-то более короткое.

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

И короткий ответ на этот вопрос: Нет.

Позвольте мне высказать мнение по этому вопросу: такого механизма не должно быть.

Это усложнит вызовы методов повсюду.

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

По сути, вы бы видели этот тип кода повсюду (гипотетически, поскольку он недоступен / не поддерживается:)

if (ReferenceCount(obj) == 1) return; // only reference is the one we have

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

Это похоже на код, который выглядит так:

1 + 2;

Что делает этот код? Ну, в зависимости от компилятора C / C ++, он может скомпилировать что-то, что оценивает 1 + 2. Но что тогда, где хранится результат? Вы используете это для чего-нибудь? Нет? Тогда почему этот код является частью вашего исходного кода для начала?

Конечно, вы могли бы утверждать, что код на самом деле a+b;, и цель состоит в том, чтобы гарантировать, что оценка a+b не вызовет исключение, обозначающее переполнение, но такой случай настолько убывающий что особый случай для этого просто замаскирует реальные проблемы, и было бы действительно просто исправить, просто присвоив его временной переменной.

В любом случае, для любой функции любого языка программирования и / или среды выполнения и / или среды, где функция недоступна, причины ее недоступности:

  • Он не был спроектирован должным образом
  • Не указано правильно
  • Это не было реализовано должным образом
  • Это не было проверено должным образом
  • Это не было задокументировано должным образом
  • Не было приоритета над конкурирующими функциями

Все это необходимо для того, чтобы функция появилась в версии X приложения Y, будь то C # 4.0 или MS Works 7.0.

3 голосов
/ 18 декабря 2009

Я могу придумать, как это сделать, но я бы определенно не рекомендовал это. Просто ради аргумента.

Что значит для объекта быть "новым" объектом? Это означает, что есть только одна ссылка, поддерживающая его. «Существующий» объект будет иметь более одной ссылки на него.

Имея это в виду, посмотрите на следующий код:

    class Program
    {
        static void Main(string[] args)
        {
            object o = new object();

            Console.WriteLine(IsExistingObject(o));
            Console.WriteLine(IsExistingObject(new object()));

            o.ToString();  // Just something to simulate further usage of o.  If we didn't do this, in a release build, o would be collected by the GC.Collect call in IsExistingObject. (not in a Debug build)
        }

        public static bool IsExistingObject(object o)
        {
            var oRef = new WeakReference(o);

#if DEBUG 
            o = null; // In Debug, we need to set o to null.  This is not necessary in a release build.
#endif
            GC.Collect();
            GC.WaitForPendingFinalizers();

            return oRef.IsAlive;
        }
    }

Это печатает True в первой строке, False во второй. Но опять же, пожалуйста, не используйте это в своем коде.

3 голосов
/ 17 декабря 2009

Краткий ответ - нет, нет

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

// For example create a method that allows you to do this:
people.Add("Larry");

// Instead of this:
people.Add(new Person("Larry"));

// The new method might look a little like this:
public void Add(string name)
{
    Person person = new Person(name);
    this.add(person); // This method could be private if neccessary
}
0 голосов
/ 17 декабря 2009

Один из способов узнать, был ли объект, переданный функции (или методу), создан непосредственно перед вызовом функции / метода, состоит в том, что объект имеет свойство, которое инициализируется меткой времени, переданной из системной функции; таким образом, глядя на это свойство, можно было бы решить проблему.

Честно говоря, я бы не использовал такой метод, потому что

  • Я не вижу причин, почему код должен теперь, если переданный параметр является объектом, созданным правильно, или если он был создан в другой момент.
  • Метод, который я предлагаю, зависит от системной функции, которая в некоторых системах может отсутствовать или которая может быть менее надежной.
  • В современных процессорах, которые работают намного быстрее, чем процессоры, которые использовались 10 лет назад, может возникнуть проблема с использованием правильного значения для порогового значения, чтобы определить, был ли объект недавно создан или нет.

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

Еще раз я спрашивал себя: «Действительно ли это нужно делать?».

0 голосов
/ 17 декабря 2009

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

0 голосов
/ 17 декабря 2009

Нет, нет способа узнать.

Все, что передается, это ссылка на объект. Является ли он «новым» на месте или полученным из массива, у рассматриваемого метода нет никакого способа узнать, как были переданы экземпляры передаваемых параметров и / или где.

...