2010년 2월 8일 월요일

[Tips]Careless reuse of variables

이 문서는 ArcGIS 9.3. 버전을 기준으로 작성되었으며, .NET C#(3.0) 샘플 코드 실행을 위해서는 다음의 어셈블리를 참조해야 한다.
- ESRI.ArcGIS.ADF
- ESRI.ArcGIS.Geodatabase
- ESRI.ArcGIS.System (ESRI.ArcGIS.esriSystem)

이 문서는 Geodatabase API를 사용하는 개발자들에게 CheatSheet(커닝페이퍼?)을 제공할 목적으로 작성되었으며 주요 내용은 성능향상, 범하기 쉬운 실수 등을 제공한다.
예제 코드는 특정 상황에 대한 샘플 코드로서 패턴을 제공하고, 상황에 따라 오류를 비교하기 위해 임의의 오류를 포함하고 있으므로 이 코드를 재활용해서는 안된다.
 
⑧ Careless reuse of variables
Geodatabase API를 사용할 때 부주의한 변수의 재사용은 2가지 유형의 문제를 발생시킨다.

첫번째 유형은 Fields(Sets of fields)와 같은 컬렉션을 생성할 때 발생할 수 있다.

Object ID와 String 필드를 포함하는 Fields를 생성한 후 ITable을 생성하는 아래 코드를 살펴보자.

[code c#]
private void FieldSetCreation()
{
    IWorkspace sourceWs = OpenWorkspace();

    // Create a new field collection and a field.
    IFields fields = new FieldsClass();
    IFieldsEdit fieldsEdit = (IFieldsEdit)fields;
    IField field = new FieldClass();
    IFieldEdit fieldEdit = (IFieldEdit)field;
   
    // Add an ObjectID field.
    fieldEdit.Name_2 = "OBJECTID";
    fieldEdit.Type_2 = esriFieldType.esriFieldTypeOID;
    fieldsEdit.AddField(field);
   
    // Add a text field.
    fieldEdit.Name_2 = "NAME";
    fieldEdit.Type_2 = esriFieldType.esriFieldTypeString;
    fieldsEdit.AddField(field);
   
    IFeatureWorkspace featureWs = (IFeatureWorkspace)sourceWs;

    ITable newTable = featureWs.CreateTable("MY_TABLE", fields, null, null, string.Empty);
}
[/code]
이 코드는 CreateTable 메쏘드를 호출하는 부분에서 다음의 오류를 발생한다.

왜 위 코드가 제대로 실행되지 않고 오류가 발생할까요? 다음의 코드를 이용하여 필드 정보를 확인하면 어떤 결과가 나올까요?
[code c#]
for (int k = 0; k < fields.FieldCount; k++)
{
    field = fields.get_Field(k);
    Debug.WriteLine(string.Format("{0} : {1}", field.Name, field.Type.ToString()));
}
[/code]

아래는 위 코드의 결과이다.

펼쳐두기..

테이블을 생성하는 위 코드 호출시 발생하는 오류 메세지는 HREULT EFAIL 오류만 표시하고 자세한 오류내용을 알려주지는 않지만, 이 오류는 중복필드로 인해 발생하는 문제이다. 즉, 2개의 Name이라는 String 필드를 이용하여 테이블을 생성하려고 했기 때문이다. 이는 "field"와 "fieldEdit"변수는 첫번째 추가한 Object ID 필드를 참조하기 때문이며 String 필드를 설정하면서 field 객체가 변경되고 같은 객체가 사용된 것이다.

이 오류는 2가지의 방법으로 해결할 수 있다.

 -
각 필드를 추가할 때 마다 field와 fieldEdit 변수를 새로 생성하여 할당한다.
 - oidField와 oidFieldEdit처럼 생성할 각 필드마다 다른 변수를 사용한다.

다음은 첫번째 방법으로 오류를 수정한 코드이다. text field를 생성할 때 field와 fieldEdit을 새로 할당하였다.

펼쳐두기..


부주의한 변수 사용으로 발생되는 두번째 유형의 문제는 ComReleaser나 Marshal.ReleaseComObject 메쏘드에 의해 명시적으로 해제되어야 하는 객체의 모든 참조를 잃어버리는 경우이다.

아래 코드를 살펴보자.

[code c#]
private static void CursorReassignment(IFeatureClass featureClass, IQueryFilter  queryFilter)
{
  // Execute a query...
  IFeatureCursor featureCursor = featureClass.Search(queryFilter, true);
  IFeature feature = null;
  while ((feature = featureCursor.NextFeature()) != null)
  {
    // Do something with the feature...
  }

  // Re-execute the query...
  featureCursor = featureClass.Search(queryFilter, true);
  feature = null;
  while ((feature = featureCursor.NextFeature()) != null)
  {
    // Do something with the feature...
  }

  // Release the cursor.
  Marshal.ReleaseComObject(featureCursor);
}
[/code]
이런 상황에서 발생하는 문제는 두번째 Cursor만 해제된다는 것이다. 첫번째 Cursor에 대한 참조는 잃어버렸기 때문에, 첫번째 Cursor는 non-deterministic 가비지 컬렉션에 의존적이 된다.
위와 같은 문제는
ComReleaser 클래스가 사용되는 곳에는 언제든지 발생할 수 있다. Llifetime 관리는 객체이지 변수가 아니다.
위 코드에서는 두번째 쿼리를 수행하기 전에
Marshal.ReleaseComObject(featureCursor);를 삽입하면 된다.

아래 예제에서는 첫번째 Cursor만 ComReleaser에 의해
관리되는 문제점이 있다. 두번째 커서를 호출한 후 comReleaser.ManageLifetime(featureCursor);를 삽입하면 문제가 해결된다.
[code c#]
private static void CursorReassignment(IFeatureClass featureClass, IQueryFilter  queryFilter)
{
  using(ComReleaser comReleaser = new ComReleaser())
  {
    // Execute a query...
    IFeatureCursor featureCursor = featureClass.Search(queryFilter, true);
    comReleaser.ManageLifetime(featureCursor);
    IFeature feature = null;
    while ((feature = featureCursor.NextFeature()) != null)
    {
      // Do something with the feature...
    }

    // Re-execute the query...
    featureCursor = featureClass.Search(queryFilter, true);
    feature = null;
    while ((feature = featureCursor.NextFeature()) != null)
    {
      // Do something with the feature...
    }
  }
}
[/code]
출처 : ArcGIS Resource Center