Использование «ref» и / или «out» для типа объекта - PullRequest
2 голосов
/ 28 октября 2008

Я застрял с приложением .Net 1.1 (т. Е. Пока не могу использовать новинки от 2.0), и я пытался оптимизировать некоторые части кода. Так как он имеет дело с вызываемыми обертками во время выполнения, которые должны быть освобождены, я в итоге создал метод утилиты, который зацикливается до тех пор, пока не будут освобождены все ссылки. Подпись метода:

void ReleaseObject(object comObject)

После освобождения всех comObjects я вызываю GC.Collect и GC.WaitForPendingFinalizers (не спрашивайте - кто-нибудь, имеющий дело с взаимодействием Office, знает).

И ... как обычно, я заглядываю в угловой случай - если я не назначаю соответствующую управляемую ссылку для null перед вызовом GC.Collect, она не очищается должным образом.

Итак, мой код выглядит так:

ReleaseObject(myComObject);
myComObject = null;
GC.Collect()
...

Так как существует множество xxx = null, я решил поместить это в метод util, но, поскольку есть разница между передачей по ссылке и передачей ссылочного параметра, очевидно, мне пришлось изменить метод на:

void ReleaseObject(out object comObject)
{
   //do release
   comObject = null;
}

и измените вызывающего абонента на:

MyComClass myComObject = xxxx;
ReleaseObject(out myComObject);

Это завершается ошибкой с сообщением: «Не удается преобразовать из« MyComClass »в« объект »»

Хотя я могу думать о том, почему это может быть проблемой (т. Е. Обратное приведение объекта к MyComClass не является неявным, и нет никакой гарантии, что будет делать метод), мне было интересно, есть ли обходной путь, или я нужно остаться с моими сотнями присвоений нулей.

Примечание: у меня есть куча разных типов COM-объектов, поэтому мне нужен параметр "object", а не безопасный тип.

Ответы [ 4 ]

3 голосов
/ 29 октября 2008

Sunny, ref и out - это маршалинг-подсказка + контракт с компилятором. Ref и out - перенос в дни COM - подсказки для объектов при отправке по проводам / между процессами.

Контракт out

void foo( out MyClass x)
  1. foo () установит для x значение, прежде чем оно вернется.
  2. x не имеет значения при вводе foo (), и вы получите ошибку компилятора, если попытаетесь использовать x перед его установкой. (использование неназначенного параметра x)

Контракт ref

void foo( ref MyClass x)
  1. ref позволяет изменить ссылку на вызывающего абонента.
  2. х должен быть назначен
    • вы не можете привести что-либо к промежуточной переменной foo (ref (object) что-то)
    • х не может быть собственностью

Реальность последних двух пунктов, скорее всего, помешает вам делать то, что вы пытаетесь сделать, потому что в действительности они не имеют смысла, когда вы понимаете, какие на самом деле ссылки. Если вы хотите это знать, спросите Джона Скита (он написал книгу).

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


ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ

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


Это сказал ..

Альтернатива 1

Ref не справится, если вы не предоставите перегрузки для каждого типа (com) объекта, с которым будете вызывать его.

// need a remove method for each type. 
void Remove( ref Com1 x ) { ...; x = null; }
void Remove( ref Con2 x ) { ...; x = null; }
void Remove( ref Com3 x ) { ...; x = null; }

// a generics version using ref.
void RemoveComRef<ComT>(ref ComT t) where ComT : class
{
    System.Runtime.InteropServices.Marshal.ReleaseComObject(t);
    t = null; 
}

Com1 c1 = new Com1();
Com2 c2 = new Com2();
Remove( ref c1 );
RemoveComRef(ref c2); // the generics version again.

Альтернатива 2

Если вы не хотите этого делать, верните null из метода Remove () и приведите обратно к типу объекта.

class Remover
{
    // .net 1.1 must cast if assigning
    public static object Remove(object x)
    {
        System.Runtime.InteropServices.Marshal.ReleaseComObject(x);
        return null;
    }

    // uses generics.
    public static ComT RemoveCom<ComT>(ComT t) where ComT : class
    {
        System.Runtime.InteropServices.Marshal.ReleaseComObject(t);
        return null;
    }   
}

Com1 c1 = new Com1();
Com2 c2 = new Com2();
c1 = (Com1)Remover.Remove(c1); // no reliance on generics
c2 = Remover.RemoveCom(c2); // relies on generics

* Я добавил общие версии для сравнения.

Приведенный выше код приводит к тому, что при взгляде на код вы становитесь подозрительным, когда видите вызов Remove (x) без присваивания (что делает неправильный код неправильным). Вы могли бы даже Grep через кодовую базу, ища вызовы Удалить, где назначение не имеет места.


ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ - все вышеизложенное основано на том, что вам нужно вручную установить нулевую ссылку, что (как правило) не требуется.

3 голосов
/ 28 октября 2008

Почему лучше вызывать метод, чем просто установить переменную в null? Они оба являются однолинейными вызовами, и последний намного проще.

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

RCW не поддерживают IDisposable? Если это так, лучше всего позвонить в Dispose (желательно с помощью оператора using).

(После обсуждения в комментариях.)

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

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

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

По моему мнению, вы не сможете установить для этих объектов значение null в другом методе (кстати, вам нужно использовать параметр ref вместо out , чтобы он работал, в любом случае вы столкнетесь с той же проблемой с ошибкой «Не удалось преобразовать ...».) Я бы порекомендовал создать и массив объектов, а затем перебрать этот массив, вызвав метод ReleaseObject и установив для этих объектов значение null. Что-то вроде:

List garbagedObjects = new List();
garbagedObjects.Add(myComObject1);
garbagedObjects.Add(myComObject2);
...
foreach(object garbagedObject in garbagedObjects)
{
  ReleaseObject(garbagedObject);
  garbagedObject = null;
}
garbagedObjects = null;
GC.Collect();
...
0 голосов
/ 29 октября 2008

Вы должны звонить Marshal.ReleaseComObject, который AFAIK был доступен в версии 1.1.

Вы, вероятно, имеете в виду "ref":

static void ReleaseObject(ref object comObject)
{
   if(comObject != null)
   {
     //do release
     comObject = null;
   }
}

[edit re comments], однако, это будет работать только для нетипизированных объектов, так что не стоит использовать без обобщений! Ох за C # 2.0 ...

Re "ref"; если переменные действительно переменные (то есть переменные метода), то они вскоре выйдут из области видимости и будут собраны. «Ссылка» будет полезна только для освобождения полей. Но, честно говоря, было бы проще установить их в нуль ...

Типичный шаблон COM:

SomeType obj = new SomeType();
try {
  obj.SomeMethod(); // etc
} finally {
  Marshal.ReleaseComObject(obj);
}
...