c#의 4가지 큰 특징

c sharp 2005. 6. 14. 08:23

C# 2.0의 큰 특징은 4가지로 정리가 된다.

1. Generics (java 5.0에서 언어차원으로, C#은 vm차원으로 지원) - ECMA를 참조해야 한다.

2. Iterator (자바 5.0 에는 for문으로 사용, c#에서는 foreach라고 두었음)

3. partial types(c#에만 있음)

4. anonymous method (delegate 없이 메소드 바로 호출, 자바 5.0 에서도 가능)

 

출처 : http://msdn.microsoft.com/msdnmag/issues/04/05/c20/default.aspx

# 2.0

Create Elegant Code with Anonymous Methods, Iterators, and Partial Classes


This article was based on a pre-release version of Microsoft Visual Studio 2005, formerly code-named "Whidbey." All information contained herein is subject to change.

This article discusses:
  • Iterating over collections
  • Spanning class definitions across files
  • Anonymous methods for use with delegates
  • Other new C# features in Visual Studio 2005
This article uses the following technologies:
C# and Visual Studio


Code download available at:
C20.exe (164KB)

ans of the C# language will find much to like in Visual C#® 2005. Visual Studio® 2005 brings a wealth of exciting new features to Visual C# 2005, such as generics, iterators, partial classes, and anonymous methods. While generics is the most talked-about and anticipated feature, especially among C++ developers who are familiar with templates, the other new features are important additions to your Microsoft® .NET development arsenal as well. These features and language additions will improve your overall productivity compared to the first version of C#, leaving you to write cleaner code faster. For some background information on generics, you should take a look at the sidebar "What are Generics?"


Iterators

In C# 1.1, you can iterate over data structures such as arrays and collections using a foreach loop:

string[] cities = {"New York","Paris","London"};
foreach(string city in cities)
{
   Console.WriteLine(city);
}

In fact, you can use any custom data collection in the foreach loop, as long as that collection type implements a GetEnumerator method that returns an IEnumerator interface. Usually you do this by implementing the IEnumerable interface:

public interface IEnumerable 
{
   IEnumerator GetEnumerator();
}
public interface IEnumerator 
{
   object Current{get;}
   bool MoveNext();
   void Reset();
}

Often, the class that is used to iterate over a collection by implementing IEnumerable is provided as a nested class of the collection type to be iterated. This iterator type maintains the state of the iteration. A nested class is often better as an enumerator because it has access to all the private members of its containing class. This is, of course, the Iterator design pattern, which shields iterating clients from the actual implementation details of the underlying data structure, enabling the use of the same client-side iteration logic over multiple data structures, as shown in Figure 1.

Figure 1 Iterator Design Pattern
Figure 1 Iterator Design Pattern

In addition, because each iterator maintains separate iteration state, multiple clients can execute separate concurrent iterations. Data structures such as the Array and the Queue support iteration out of the box by implementing IEnumerable. The code generated in the foreach loop simply obtains an IEnumerator object by calling the class's GetEnumerator method and uses it in a while loop to iterate over the collection by continually calling its MoveNext method and current property. You can use IEnumerator directly (without resorting to a foreach statement) if you need explicit iteration over the collection.

But there are some problems with this approach. The first is that if the collection contains value types, obtaining the items requires boxing and unboxing them because IEnumerator.Current returns an Object. This results in potential performance degradation and increased pressure on the managed heap. Even if the collection contains reference types, you still incur the penalty of the down-casting from Object. While unfamiliar to most developers, in C# 1.0 you can actually implement the iterator pattern for each loop without implementing IEnumerator or IEnumerable. The compiler will choose to call the strongly typed version, avoiding the casting and boxing. The result is that even in version 1.0 it's possible not to incur the performance penalty.

To better formulate this solution and to make it easier to implement, the Microsoft .NET Framework 2.0 defines the generic, type-safe IEnumerable<ItemType> and IEnumerator<ItemType> interfaces in the System.Collections.Generics namespace:

public interface IEnumerable<ItemType>
{   
   IEnumerator<ItemType> GetEnumerator();
} 
public interface IEnumerator<ItemType> : IDisposable
{
   ItemType Current{get;}
   bool MoveNext();
}       

Besides making use of generics, the new interfaces are slightly different than their predecessors. Unlike IEnumerator, IEnumerator<ItemType> derives from IDisposable and does not have a Reset method. The code in Figure 2 shows a simple city collection implementing IEnumerable<string>, and Figure 3 shows how the compiler uses that interface when spanning the code of the foreach loop. The implementation in Figure 2 uses a nested class called MyEnumerator, which accepts as a construction parameter a reference back to the collection to be enumerated. MyEnumerator is intimately aware of the implementation details of the city collection, an array in this example. The MyEnumerator class maintains the current iteration state in the m_Current member variable, which is used as an index into the array.

The second and more difficult problem is implementing the iterator. Although that implementation is straightforward for simple cases (as shown in Figure 3), it is challenging with more advanced data structures, such as binary trees, which require recursive traversal and maintaining iteration state through the recursion. Moreover, if you require various iteration options, such as head-to-tail and tail-to-head on a linked list, the code for the linked list will be bloated with various iterator implementations. This is exactly the problem that C# 2.0 iterators were designed to address. Using iterators, you can have the C# compiler generate the implementation of IEnumerator for you. The C# compiler can automatically generate a nested class to maintain the iteration state. You can use iterators on a generic collection or on a type-specific collection. All you need to do is tell the compiler what to yield in each iteration. As with manually providing an iterator, you need to expose a GetEnumerator method, typically by implementing IEnumerable or IEnumerable<ItemType>.

You tell the compiler what to yield using the new C# yield return statement. For example, here is how you use C# iterators in the city collection instead of the manual implementation of Figure 2:

public class CityCollection : IEnumerable<string>
{
   string[] m_Cities = {"New York","Paris","London"};
   public IEnumerator<string> GetEnumerator()
   {
      for(int i = 0; i<m_Cities.Length; i++)
         yield return m_Cities[i];
   }
}
You can also use C# iterators on non-generic collections:
public class CityCollection : IEnumerable
{
   string[] m_Cities = {"New York","Paris","London"};
   public IEnumerator GetEnumerator()
   {
      for(int i = 0; i<m_Cities.Length; i++)
         yield return m_Cities[i];
   }
}

In addition, you can use C# iterators on fully generic collections, as shown in Figure 4. When using a generic collection and iterators, the specific type used for IEnumerable<ItemType> in the foreach loop is known to the compiler from the type used when declaring the collection—a string in this case:

LinkedList<int,string> list = new LinkedList<int,string>();
/* Some initialization of list, then  */
foreach(string item in list)
{   
   Trace.WriteLine(item);
}
This is similar to any other derivation from a generic interface.

If for some reason you want to stop the iteration midstream, use the yield break statement. For example, the following iterator will only yield the values 1, 2, and 3:

public IEnumerator<int> GetEnumerator()
{
   for(int i = 1;i< 5;i++)
   {
      yield return i;
      if(i > 2)
         yield break;
   }
}

Your collection can easily expose multiple iterators, each used to traverse the collection differently. For example, to traverse the CityCollection class in reverse order, provide a property of type IEnumerable<string> called Reverse:

public class CityCollection 
{   
   string[] m_Cities = {"New York","Paris","London"};
   public IEnumerable<string> Reverse
   {
      get
      {
         for(int i=m_Cities.Length-1; i>= 0; i--)
            yield return m_Cities[i];         
      }
   }
}  
Then use the Reverse property in a foreach loop:
CityCollection collection = new CityCollection();
foreach(string city in collection.Reverse)
{   
   Trace.WriteLine(city);
}

There are some limitations to where and how you can use the yield return statement. A method or a property that has a yield return statement cannot also contain a return statement because that would improperly break the iteration. You cannot use yield return in an anonymous method, nor can you place a yield return statement inside a try statement with a catch block (and also not inside a catch or a finally block).


Iterator Implementation

The compiler-generated nested class maintains the iteration state. When the iterator is first called in a foreach loop (or in direct iteration code), the compiler-generated code for GetEnumerator creates a new iterator object (an instance of the nested class) with a reset state. Every time the foreach loops and calls the iterator's MoveNext method, it begins execution where the previous yield return statement left off. As long as the foreach loop executes, the iterator maintains its state. However, the iterator object (and its state) does not persist across foreach loops. Consequently, it is safe to call foreach again because you will get a new iterator object to start the new iteration. This is why IEnumerable<ItemType> does not define a Reset method.

But how is the nested iterator class implemented and how does it manage its state? The compiler transforms a standard method into a method that is designed to be called multiple times and that uses a simple state machine to resume execution after the previous yield statement. All you have to do is indicate what and when to yield to the compiler using the yield return statement. The compiler is even smart enough to concatenate multiple yield return statements in the order they appear:

public class CityCollection : IEnumerable<string>
{
   public IEnumerator<string> GetEnumerator()
   {
      yield return "New York";
      yield return "Paris";
      yield return "London";
   }
}

Let's take a look at the GetEnumerator method of the class shown in the following lines of code:

public class MyCollection : IEnumerable<string>
{
   public IEnumerator<string> GetEnumerator()
   {
      //Some iteration code that uses yield return 
   }
}
When the compiler encounters a class member with a yield return statement such as this, it injects the definition of a nested class called GetEnumerator$<random unique number>__IEnumeratorImpl, as shown in the C# pseudocode in Figure 5. (Remember that all of the features discussed in this article—the names of the compiler-generated classes and fields—are subject to change, in some cases quite drastically. You should not attempt to use reflection to get at those implementation details and expect consistent results.)

The nested class implements the same IEnumerable interface returned from the class member. The compiler replaces the code in the class member with an instantiation of the nested type, assigning to the nested class's <this> member variable a reference back to the collection, similar to the manual implementation shown in Figure 2. The nested class is actually the one providing the implementation of IEnumerator.


Recursive Iterations

Iterators really shine when it comes to iterating recursively over a data structure such as a binary tree or any complex graph of interconnecting nodes. With recursive iteration, it is very difficult to manually implement an iterator, yet using C# iterators it is done with great ease. Consider the binary tree in Figure 6. The full implementation of the tree is part of the source code available with this article.

The binary tree stores items in nodes. Each node holds a value of the generic type T, called Item. Each node has a reference to a node on the left and a reference to a node on the right. Values smaller than Item are stored in the left-side subtree, and larger values are stored in the right-side subtree. The tree also provides an Add method for adding an open-ended array of values of the type T, using the params qualifier:

public void Add(params T[] items);

The tree provides a public property called InOrder of type IEnumerable<T>. InOrder calls the recursive private helper method ScanInOrder, passing to ScanInOrder the root of the tree. ScanInOrder is defined as:

IEnumerable<T> ScanInOrder(Node<T> root);
It returns the implementation of an iterator of the type IEnumerable<T>, which traverses the binary tree in order. The interesting thing about ScanInOrder is the way it uses recursion to iterate over the tree using a foreach loop that accesses the IEnumerable<T> returned from a recursive call. With in-order iteration, every node iterates over its left-side subtree, then over the value in the node itself, then over the right-side subtree. For that, you need three yield return statements. To iterate over the left-side subtree, ScanInOrder uses a foreach loop over the IEnumerable<T> returned from a recursive call that passes the left-side node as a parameter. Once that foreach loop returns, all the left-side subtree nodes have been iterated over and yielded. ScanInOrder then yields the value of the node passed to it as the root of the iteration and performs another recursive call inside a foreach loop, this time on the right-side subtree.

The InOrder property allows you to write the following foreach loop to iterate over the entire tree:

BinaryTree<int> tree = new BinaryTree<int>();
tree.Add(4,6,2,7,5,3,1);
foreach(int num in tree.InOrder)
{
   Trace.WriteLine(num);
}
// Traces 1,2,3,4,5,6,7
You can implement pre-order and post-order iterations in a similar manner by adding additional properties.

While the ability to use iterators recursively is obviously a powerful feature, it should be used with care as there can be serious performance implications. Each call to ScanInOrder requires an instantiation of the compiler-generated iterator, so recursively iterating over a deep tree could result in a large number of objects being created behind the scenes. In a balanced binary tree, there are approximately n iterator instantiations, where n is the number of nodes in the tree. At any given moment, approximately log(n) of those objects are live. In a decently sized tree, a large number of those objects will make it past the Generation 0 garbage collection. That said, iterators can still be used to easily iterate over recursive data structures such as trees by using stacks or queues to maintain a list of nodes still to be examined.


Partial Types

C# 1.1 requires you to put all the code for a class in a single file. C# 2.0 allows you to split the definition and implementation of a class or a struct across multiple files. You can put one part of a class in one file and another part of the class in a different file, noting the split by using the new partial keyword. For example, you can put the following code in the file MyClass1.cs:

public partial class MyClass
{
   public void Method1()
   {...}
}
In the file MyClass2.cs, you can insert this code:
public partial class MyClass
{
   public void Method2()
   {...}
   public int Number;
}

In fact, you can have as many parts as you like in any given class. Partial type support is available for classes, structures, and interfaces, but you cannot have a partial enum definition.

Partial types are a very handy feature. Sometimes it is necessary to modify a machine-generated file, such as a Web service client-side wrapper class. However, changes made to the file will be lost if you regenerate the wrapper class. Using a partial class, you can factor those changes into a separate file. ASP.NET 2.0 uses partial classes for the code-beside class (the evolution of codebehind), storing the machine-generated part of the page separately. Windows® Forms uses partial classes to store the visual designer output of the InitializeComponent method as well as the member controls. Partial types also enable two or more developers to work on the same type while both have their files checked out from source control without interfering with each other.

You may be asking yourself, what if the various parts define contradicting aspects of the class? The answer is simple: a class (or a struct) can have two kinds of aspects or qualities: accumulative and non-accumulative. The accumulative aspects are things that each part of the class can choose to add, such as interface derivation, properties, indexers, methods, and member variables.

For example, the following code shows how a part can add interface derivation and implementation:

public partial class MyClass
{}
public partial class MyClass : IMyInterface
{
   public void Method1()
   {...}
   public void Method2()
   {...}
}

The non-accumulative aspects are things that all the parts of a type must agree upon. Whether the type is a class or a struct, type visibility (public or internal) and the base class are non-accumulative aspects. For example, the following code does not compile because not all the parts of MyClass concur on the base class:

public class MyBase
{}
public class SomeOtherClass
{}
public partial class MyClass : MyBase
{}
public partial class MyClass : MyBase
{}
//Does not compile
public partial class MyClass : SomeOtherClass
{}
In addition to having all parts define the same non-accumulative parts, only a single part can override a virtual or an abstract method, and only one part can implement an interface member.

C# 2.0 supports partial types as follows: when the compiler builds the assembly, it combines from the various files the parts of a type and compiles them into a single type in Microsoft intermediate language (MSIL). The generated MSIL has no recollection which part came from which file. Just like in C# 1.1 the MSIL has no record of which file was used to define which type. Also worth noting is that partial types cannot span assemblies, and that a type can refuse to have other parts by omitting the partial qualifier from its definition.

Because all the compiler is doing is accumulating parts, a single file can contain multiple parts, even of the same type, although the usefulness of that is questionable.

In C#, developers often name a file after the class it contains and avoid putting multiple classes in the same file. When using partial types, I recommend indicating in the file name that it contains parts of a type such as MyClassP1.cs, MyClassP2.cs, or employing some other consistent way of externally indicating the content of the source file. For example, the Windows Forms designer stores its portion of the partial class for the form in Form1.cs to a file named Form1.Designer.cs.

Another side effect of partial types is that when approaching an unfamiliar code base, the parts of the type you maintain could be spread all over the project files. In such cases, my advice is to use the Visual Studio Class View because it displays an accumulative view of all the parts of the type and allows you to navigate through the various parts by clicking on its members. The navigation bar provides this functionality as well.


Anonymous Methods

C# supports delegates for invoking one or multiple methods. Delegates provide operators and methods for adding and removing target methods, and are used extensively throughout the .NET Framework for events, callbacks, asynchronous calls, and multithreading. However, you are sometimes forced to create a class or a method just for the sake of using a delegate. In such cases, there is no need for multiple targets, and the code involved is often relatively short and simple. Anonymous methods is a new feature in C# 2.0 that lets you define an anonymous (that is, nameless) method called by a delegate.

For example, the following is a conventional SomeMethod method definition and delegate invocation:

class SomeClass
{
   delegate void SomeDelegate();
   public void InvokeMethod()
   {
      SomeDelegate del = new SomeDelegate(SomeMethod);      
      del(); 
   }
   void SomeMethod()   
   {      
      MessageBox.Show("Hello");
   }
}
You can define and implement this with an anonymous method:
class SomeClass
{
   delegate void SomeDelegate();
   public void InvokeMethod()
   {
      SomeDelegate del = delegate() 
                         {
                             MessageBox.Show("Hello");
                         };
      del();   
   }
}

The anonymous method is defined in-line and not as a member method of any class. Additionally, there is no way to apply method attributes to an anonymous method, nor can the anonymous method define generic types or add generic constraints.

You should note two interesting things about anonymous methods: the overloaded use of the delegate reserved keyword and the delegate assignment. You will see later on how the compiler implements an anonymous method, but it is quite clear from looking at the code that the compiler has to infer the type of the delegate used, instantiate a new delegate object of the inferred type, wrap the new delegate around the anonymous method, and assign it to the delegate used in the definition of the anonymous method (del in the previous example).

Anonymous methods can be used anywhere that a delegate type is expected. You can pass an anonymous method into any method that accepts the appropriate delegate type as a parameter:

class SomeClass
{
   delegate void SomeDelegate();
   public void SomeMethod()   
   {      
      InvokeDelegate(delegate(){MessageBox.Show("Hello");});   
   }   
   void InvokeDelegate(SomeDelegate del)   
   {      
      del();   
   }
}
If you need to pass an anonymous method to a method that accepts an abstract Delegate parameter, such as the following
void InvokeDelegate(Delegate del);
first cast the anonymous method to the specific delegate type.

A concrete and useful example for passing an anonymous method as a parameter is launching a new thread without explicitly defining a ThreadStart delegate or a thread method:

public class MyClass
{   
   public void LauchThread()
   {
      Thread workerThread = new Thread(delegate()
                            {
                                MessageBox.Show("Hello");
                            });      
      workerThread.Start();
   }
}
In the previous example, the anonymous method serves as the thread method, causing the message box to be displayed from the new thread.


Passing Parameters to Anonymous Methods

When defining an anonymous method with parameters, you define the parameter types and names after the delegate keyword just as if it were a conventional method. The method signature must match the definition of the delegate to which it is assigned. When invoking the delegate, you pass the parameter's values, just as with a normal delegate invocation:

class SomeClass
{
   delegate void SomeDelegate(string str);
   public void InvokeMethod()
   {
      SomeDelegate del = delegate(string str)
                         {
                             MessageBox.Show(str);
                         };
      del("Hello");   
   }
}

If the anonymous method has no parameters, you can use a pair of empty parens after the delegate keyword:

class SomeClass
{
   delegate void SomeDelegate();
   public void InvokeMethod()
   {
      SomeDelegate del = delegate()
                         {
                             MessageBox.Show("Hello");
                         };
      del();   
   }
}
However, if you omit the empty parens after the delegate keyword altogether, you are defining a special kind of anonymous method, which could be assigned to any delegate with any signature:
class SomeClass
{
   delegate void SomeDelegate(string str);
   public void InvokeMethod()
   {
      SomeDelegate del = delegate                         
                         {
                             MessageBox.Show("Hello");
                         };
      del("Parameter is ignored");   
   }
}

Obviously, you can only use this syntax if the anonymous method does not rely on any of the parameters, and you would want to use the method code regardless of the delegate signature. Note that you must still provide arguments when invoking the delegate because the compiler generates nameless parameters for the anonymous method, inferred from the delegate signature, as if you wrote the following (in C# pseudocode):

SomeDelegate del = delegate(string) 
                   {
                       MessageBox.Show("Hello");
                   };
Additionally, anonymous methods without a parameter list cannot be used with delegates that specify out parameters.

An anonymous method can use any class member variable, and it can also use any local variable defined at the scope of its containing method as if it were its own local variable. This is demonstrated in Figure 7. Once you know how to pass parameters to an anonymous method, you can also easily define anonymous event handling, as shown in Figure 8.

Because the += operator merely concatenates the internal invocation list of one delegate to another, you can use the += to add an anonymous method. Note that with anonymous event handling, you cannot remove the event handling method using the -= operator unless the anonymous method was added as a handler by first storing it to a delegate and then registering that delegate with the event. In that case, the -= operator can be used with the same delegate to unregister the anonymous method as a handler.


Anonymous Method Implementation

The code the compiler generates for anonymous methods largely depends on which type of parameters or variables the anonymous methods uses. For example, does the anonymous method use the local variables of its containing method (called outer variables), or does it use class member variables and method arguments? In each case, the compiler will generate different MSIL. If the anonymous method does not use outer variables (that is, it only uses its own arguments or the class members) then the compiler adds a private method to the class, giving the method a unique name. The name of that method will have the following format:

<return type> __AnonymousMethod$<random unique number>(<params>)
As with other compiler-generated members, this is subject to change and most likely will before the final release. The method signature will be that of the delegate to which it is assigned.

The compiler simply converts the anonymous method definition and assignment into a normal instantiation of the inferred delegate type, wrapping the machine-generated private method:

SomeDelegate del = new SomeDelegate(__AnonymousMethod$00000000);
Interestingly enough, the machine-generated private method does not show up in IntelliSense®, nor can you call it explicitly because the dollar sign in its name is an invalid token for a C# method (but a valid MSIL token).

The more challenging scenario is when the anonymous method uses outer variables. In that case, the compiler adds a private nested class with a unique name in the format of:

__LocalsDisplayClass$<random unique number>
The nested class has a back reference to the containing class called <this>, which is a valid MSIL member variable name. The nested class contains public member variables corresponding to every outer variable that the anonymous method uses. The compiler adds to the nested class definition a public method with a unique name, in the format of:
<return type> __AnonymousMethod$<random unique number>(<params>)

The method signature will be that of the delegate to which it is assigned. The compiler replaces the anonymous method definition with code that creates an instance of the nested class and makes the necessary assignments from the outer variables to that instance's member variables. Finally, the compiler creates a new delegate object, wrapping the public method of the nested class instance, and calls that delegate to invoke the method. Figure 9 shows in C# pseudocode the compiler-generated code for the anonymous method definition in Figure 7.


Generic Anonymous Methods

An anonymous method can use generic parameter types, just like any other method. It can use generic types defined at the scope of the class, for example:

class SomeClass<T>
{   
   delegate void SomeDelegate(T t);   
   public void InvokeMethod(T t)   
   {      
      SomeDelegate del = delegate(T item){...}    
      del(t);   
   }
}

Because delegates can define generic parameters, an anonymous method can use generic types defined at the delegate level. You can specify the type to use in the method signature, in which case it has to match the specific type of the delegate to which it is assigned:

class SomeClass
{   
   delegate void SomeDelegate<T>(T t);   
   public void InvokeMethod()   
   {      
      SomeDelegate<int> del = delegate(int number) 
                              { 
                                 MessageBox.Show(number.ToString()); 
                              };      
      del(3);   
   }
}


Anonymous Method Example

Although at first glance the use of anonymous methods may seem like an alien programming technique, I have found it quite useful because it replaces the need for creating a simple method in cases where only a delegate will suffice. Figure 10 shows a real-life example of the usefulness of anonymous methods—the SafeLabel Windows Forms control.

Windows Forms relies on the underlying Win32® messages. Therefore, it inherits the classic Windows programming requirement that only the thread that created the window can process its messages. Calls on the wrong thread will always trigger an exception under Windows Forms in the .NET Framework 2.0. As a result, when calling a form or a control on a different thread, you must marshal that call to the correct owning thread. Windows Forms has built-in support for solving this predicament by having the Control base class implement the interface ISynchronizeInvoke, defined like the following:

public interface ISynchronizeInvoke 
{
   bool InvokeRequired {get;}
   IAsyncResult BeginInvoke(Delegate method,object[] args);
   object EndInvoke(IAsyncResult result);
   object Invoke(Delegate method,object[] args);
}

The Invoke method accepts a delegate targeting a method on the owning thread, and it will marshal the call to that thread from the calling thread. Because you may not always know whether you are actually executing on the correct thread, the InvokeRequired property lets you query to see if calling Invoke is required. The problem is that using ISynchronizeInvoke complicates the programming model significantly, and as a result it is often better to encapsulate the interaction with the ISynchronizeInvoke interface in controls and forms that will automatically use ISynchronizeInvoke as required.

For example, instead of a Label control that exposes a Text property, you can define the SafeLabel control which derives from Label, as shown in Figure 10. SafeLabel overrides its base class Text property. In its get and set, it checks whether Invoke is required. If so, it needs to use a delegate to access the property. That implementation simply calls the base class implementation of the property, but on the correct thread. Because SafeLabel only defines these methods so that they can be called through a delegate, they are good candidates for anonymous methods. SafeLabel passes the delegate, wrapping the anonymous methods to the Invoke method as its safe implementation of the Text property.


Delegate Inference

The C# compiler's ability to infer from an anonymous method assignment which delegate type to instantiate is an important capability. In fact, it enables yet another C# 2.0 feature called delegate inference. Delegate inference allows you to make a direct assignment of a method name to a delegate variable, without wrapping it first with a delegate object. For example, take a look at the following C# 1.1 code:

class SomeClass
{
   delegate void SomeDelegate();
   public void InvokeMethod()
   {
      SomeDelegate del = new SomeDelegate(SomeMethod);
      del();
   }
   void SomeMethod()
   {...}
}
Instead of the previous snippet, you can now write:
class SomeClass
{
   delegate void SomeDelegate();
   public void InvokeMethod()
   {
      SomeDelegate del = SomeMethod;
      del();
   }
   void SomeMethod()
   {...}
}

When you assign a method name to a delegate, the compiler first infers the delegate's type. Then the compiler verifies that there is a method by that name and that its signature matches that of the inferred delegate type. Finally, the compiler creates a new object of the inferred delegate type, wrapping the method and assigning it to the delegate. The compiler can only infer the delegate type if that type is a specific delegate type—that is, anything other than the abstract type Delegate. Delegate inference is a very useful feature indeed, resulting in concise, elegant code.

I believe that as a matter of routine in C# 2.0, you will use delegate inference rather than the old method of delegate instantiation. For example, here is how you can launch a new thread without explicitly creating a ThreadStart delegate:

public class MyClass
{   
   void ThreadMethod()   
   {...}
   public void LauchThread()
   {
      Thread workerThread = new Thread(ThreadMethod);      
      workerThread.Start();
   }
}

You can use a double stroke of delegate inference when launching an asynchronous call and providing a completion callback method, as shown in Figure 11. There you first assign the method name to invoke asynchronously into a matching delegate. Then call BeginInvoke, providing the completion callback method name instead of a delegate of type AsyncCallback.


Property and Index Visibility

C# 2.0 allows you to specify different visibility for the get and set accessors of a property or an indexer. For example, it is quite common to want to expose the get as public, but the set as protected. To do so, add the protected visibility qualifier to the set keyword. Similarly, you can define the set method of an indexer as protected, (see Figure 12).

There are a few stipulations when using property visibility. First, the visibility qualifier you apply on the set or the get can only be a stringent subset of the visibility of the property itself. In other words, if the property is public, then you can specify internal, protected, protected internal, or private. If the property visibility is protected, you cannot make the get or the set public. In addition, you can only specify visibility for the get or the set, but not both.


Static Classes

It is quite common to have classes with only static methods or members (static classes). In such cases there is no point in instantiating objects of these classes. For example, the Monitor class or class factories such as the Activator class in the .NET Framework 1.1 are static classes. Under C# 1.1, if you want to prevent developers from instantiating objects of your class you can provide only a private default constructor. Without any public constructors, no one can instantiate objects of your class:

public class MyClassFactory 
{
   private MyClassFactory()   
   {}
   static public object CreateObject()
   {...}
}

However, it is up to you to enforce the fact that only static members are defined on the class because the C# compiler will still allow you to add instance members, although they could never be used. C# 2.0 adds support for static classes by allowing you to qualify your class as static:

public static class MyClassFactory
{  
   static public T CreateObject<T>()
   {...}
}

The C# 2.0 compiler will not allow you to add a non-static member to a static class, and will not allow you to create instances of the static class as if it were an abstract class. In addition, you cannot derive from a static class. It's as if the compiler adds both abstract and sealed to the static class definition. Note that you can define static classes but not static structures, and you can add a static constructor.


Global Namespace Qualifier

It is possible to have a nested namespace with a name that matches some other global namespace. In such cases, the C# 1.1 compiler will have trouble resolving the namespace reference. Consider the following example:

namespace MyApp
{
   namespace System
   {
      class MyClass
      {
         public void MyMethod()
         {
            System.Diagnostics.Trace.WriteLine("It Works!");
         }
      }
   }
}

In C# 1.1, the call to the Trace class would produce a compilation error (without the global namespace qualifier ::). The reason the error would occur is that when the compiler tries to resolve the reference to the System namespace, it uses the immediate containing scope, which contains the System namespace but not the Diagnostics namespace. C# 2.0 allows you to use the global namespace qualifier :: to indicate to the compiler that it should start its search at the global scope. You can apply the :: qualifier to both namespaces and types, as shown in Figure 13.


Inline Warning

C# 1.1 allows you to disable specific compiler warnings using project settings or by issuing command-line arguments to the compiler. The problem here is that this is a global suppression, and as such suppresses warnings that you still want. C# 2.0 allows you to explicitly suppress and restore compiler warnings using the #pragma warning directive:

// Disable 'field never used' warning
#pragma warning disable 169
public class MyClass 
{
   int m_Number;
}
#pragma warning restore 169

Disabling warnings should be generally discouraged in production code. It is intended only for analysis when trying to isolate a problem, or when you lay out the code and would like to get the initial code structure in place without having to polish it up first. In all other cases, avoid suppressing compiler warnings. Note that you cannot programmatically override the project settings, meaning you cannot use the pragma warning directive to restore a warning that is suppressed globally.


Conclusion

The new features in C# 2.0 presented in this article are dedicated solutions, designed to address specific problems while simplifying the overall programming model. If you care about productivity and quality, then you want to have the compiler generate as much of the implementation as possible, reduce repetitive programming tasks, and make the resulting code concise and readable. The new features give you just that, and I believe they are an indication that C# is coming of age, establishing itself as a great tool for the developer who is an expert in .NET.

'c sharp' 카테고리의 다른 글

The Machine Debug Manager service 실행하기  (0) 2006.03.02
Posted by '김용환'
,

alias를 통해서, 귀찮고 반복적인 url 입력을 간단하게 한다.

 

다음과 같이 id를 지정한다.

 

<path id="a.jars">
  <fileset dir="a" includes="**/*.jar" excludes="**/*_old.jar" />
</path>

 

 

사용법은 다음과 같다.

 

 

<target name="build" depends="setup">
  <javac srcdir="${source.dir}" destdir="${compile.dir}">
    <classpath refid="a.jars" />
  </javac>
</target>

Posted by '김용환'
,

가끔씩 if문 같은 conditional statement를 ant에서 하고 싶을때가 있다. 그때는.. available이라는 녀석을 잘 사용하면 된다.

 

<available file="**/x.jar" property="isExist" />

 

isExist에 boolean 값이 저장이 된다.

 

이에 대해서. 특정 target이 실행을 하려고 하면,  다음과 사용하면 된다.

 

<target name="build.start" depends="setup" if="isExist">
  ...
</target>

<target name="build.end" depends="setup" unless="isExist">
  ...
</target>

 

 


Posted by '김용환'
,

<ant antfile="api.xml" />

 

이렇게 사용하면, api.xml 파일의 default target이 실행된다.

 

예제)

 

 <target name="doAll">
  <delete dir="lib" />
  <mkdir dir="lib" />
  <ant antfile="config/build.xml" />
</target>

Posted by '김용환'
,

1. 메뉴->도구->기본설정을 클릭

   일반/파일/도구 메뉴가 나옴

2. 파일을 선택

 

3. Setting & Syntax를 선택

 

4. File type을 선택

 

5. tab& indent 선택

   팝업창이 뜸

 

6. 탭대신 space를 넣는다는 체크박스를 클릭하여 on 시킴.



 

Posted by '김용환'
,

1. 메뉴->도구->기본설정을 클릭

   일반/파일/도구 메뉴가 나옴

2. 파일을 선택

 

   언어를 선택하고, 저장시 백업파일생성에 체크를 지우기

 



 

Posted by '김용환'
,

보통 cygwin에서 작업을 하는데, java 컴파일이나, java 클래스를 실해하고 싶을때, -classpath 다음에 classpath를 걸 부분에 쌍 따움표를 붙여줘서 실행을 하도록 한다.

이 부분을 안하면, 파이프 라인 명령어로 인식해서 실행이 되지 않다.

여러개의 classpath를 지정할때는 쌍따움표 (더블 쿼테이션)을 지정하도록 한다.

 

#/bin/sh
java -classpath "build/classes;builder/builder-config.jar;builder/builder-task.jar;builder/builder-shell.jar" com.xxx.Main

 

 

'java core' 카테고리의 다른 글

이벤트 모델  (0) 2005.09.16
[펌] JVM 관련  (0) 2005.09.15
Runtime  (0) 2005.06.06
millisecond를 가지고 구하기  (0) 2005.05.13
java 5.0 enum.  (0) 2005.05.12
Posted by '김용환'
,

 

Subversion Cheat Sheet

This Subversion cheat sheet was created during the initial setup of Subversion on Apache 2.0 on Windows and Mac OS X. A detailed tutorial covering most of the features of Subversion can be found in the online Subversion book. However, to make Subversion more useful for me, I created this Readers' Digest version.

Create a Repository

To store projects in Subversion, first you must create a repository. This must be done to a local drive on a local machine. Creating a repository on a network drive is not supported. To create a repository type:

UNIX

svnadmin create /path/to/repository

Windows

svnadmin create d:/path_to_repository

By default this sets up a Berkeley database to store the repository. Individual projects should be created as subdirectories of the repository directory (see the next section). Notice that the Windows version includes a drive letter, but also uses forward slashes instead of back slashes. The forward slashes are required even on Windows.

Add a New Project - svn import

To add a project, the Subversion documentation suggests that you create a directory structure like the following:

Picture of the Directory Structure

A root project directory contains three subdirectories, branches, tags, and trunk. Your files and directories are stored under the trunk directory.

Create the directories as described. Assuming the project directory is a subdirectory of the current directory, you would enter the following command

UNIX

svn import project file:///repository_name/project -m "First Import"

Windows

svn import project file:///d:/repository_name/project -m "First Import"

Network

svn import project http://host_name/svn_dir/repository_name/project -m "First Import"

Notice the Network example includes an svn_dir. This assumes you are using Apache 2.0 and the Subversion modules. When setting up Subversion on Apache, a virtual directory is created on the server that points to your repository directory. More information on Apache 2 setup is described later in this document.

This creates the initial project which you can work from. To get the files under version control, you must checkout a project to begin working on it.

Checking Out a Project - svn checkout

To start using the version control features check out a project into your local working directory. This is done with the following command:

UNIX

svn checkout file:///repository_name/project/trunk project

Windows

svn checkout file:///d:/repository_name/project/trunk project

Network

svn checkout http://host_name/svn_dir/repository_name/project/trunk project

In these examples, project is the name of the directory where you want to store the checked out project on your local file system.

Getting a List of Projects - svn list

To get a list of the current projects stored in a repository, you can use the following command.

UNIX

svn list --verbose file:///repository_name/project

Network

svn list --verbose http://host_name/svn_dir/repository_name/project

This will show you a list of each project directory in that repository.

Reviewing Changes - svn status

To see what files you have changed or added to your checked out work, use the update command:

UNIX

svn status

This command will give you a listing of new files, files that have been changed, and files that have been deleted. New files or deleted files must be added or removed using the add and delete commands (see more below.)

Adding New Files and Directories - svn add

When you add a new file or directory to a project that has been checked out, you must tell Subversion to include that file or directory in its version control.

UNIX

svn add file_or_dir_name

Adding a directory will add the directory and all the files and directories in it. However, this does not add the file or directory to the repository, you must still issue a commit to update the repository.

Deleting Files and Directories - svn delete

If you can add, you can also delete. If you wish to remove a file your directory from be versioned, you use the delete command:

UNIX

svn delete file_or_dir_name

Like add, you must perform a commit before the file is actually deleted from the repository.

However, the delete command does have another option not found in add. With the delete command you can remove files or directories from the repository. For example, the following command would remove a project and all the files under it.

Network

svn delete -m "Deleting project dir" http://localhost/svn_dir/repository/project_dir

This version of the command comes in particulary useful if someone has accidently imported files into the wrong place (I wouldn't know about that myself of course.)

Committing Changes - svn commit

Once you have added, deleted, or changed files or directories, you can then commit those changes to the repository. This command is pretty straightforward:

Network

svn commit -m "Saving recent changes" http://localhost/svn_dir/repository/project_dir

Updating Your Local Files - svn update

If you have a set of files checked out and would like to update them to the most recent version of files in the repository, use the update command.

Network

svn update

If there are newer files in the repository, they will overwrite any files you have locally. Before using this command, you may want to use the svn diff command to find out what the differences are between your local files and the repository.

Tagging Projects or Creating Project Specific Versions

Subversion does not track the version numbers for individual projects automatically. Instead, it tracks each update to the repository and tracks the versions of these updates. To create interim project releases, you must create "Tags" which identify a specify version of a project. This is done by making a virtual copy of a project in the tags directory. For example:

svn copy http://host_name/repos/project/trunk http://host_name/repos/project/tags/0.1.0 -m "Tagging the 0.1.0 release of the project"

This creates a sort of bookmark or snapshot which records the current state of the project. Then, you can checkout the project in this state at any time by simply referring to that release number.

To get a list of the releases for a project.

svn list http://192.168.0.4/svn/repos/prj1/tags
0.1.0/

Then to check out a release you would type:

svn list http://192.168.0.4/svn/repos/prj1/tags/0.1.0

A  0.1.0\dir1
A  0.1.0\dir1\file3
A  0.1.0\dir1\file4
A  0.1.0\file1
A  0.1.0\file2
A  0.1.0\textfile.txt
A  0.1.0\file3
		Checked out revision 13.
		

Since the project has been saved in the tags directory. Release 0.1.0 can be retrieved at any time in the future.

Basic Apache Setup

You must use Apache 2.0 to install Subversion. Just compile and copy or copy the Subversion Apache module into the Apache modules directory. The following two files must be uncommented or added to the httpd.conf file:

		LoadModule dav_module         modules/mod_dav.so
		LoadModule dav_svn_module     modules/mod_dav_svn.so
		

Next, you must setup a location directive in the httpd.conf file to associate a directory with Subversion repositories. This example uses the SVNParentPath setting to point to a parent directory which contains repository subdirectories. This is convenient as it allows you to add as many repositories as you need without having to restart Apache or modify the httpd.conf file.

		<Location /svn>
			DAV svn
			
			# All repos subdirs of d:/svn
			SVNParentPath D:/svn
		</Location>
		

Note:When using Fink to install Subversion on Mac OS X, the Subversion Apache module is stored in the Fink package listing with the prefix: libapache2. The package full name is libapache2-mod-svn. If you are using Fink, it will automatically install the modules into the correct directory.

General Notes

Below are a list of notes from initial setup and testing.

  • Subversion versions the repository, not individual projects. For example, I have two projects, project 1 and project 2, and check out each project when the current repository version is 3. I make changes to each project and commit those changes back to the repository. For each change, the revision number is incremented in the repository and its current version is now 5. The current revision of each project will also be 5 as they have no separate revision number.
  • To setup the Subversion module on Apache for Windows, I had to give the Apache Service access to the local file system. This is done on Windows by setting up a login account for the service. Setup an account in the Users application in Control Panel, make sure to set the password. Once this is done, go to the Services tool in Control Panel. Change the login for the Service to the account you created. XP will automatically give the Login as a Service privilege to the account (the OS must do this as the tools are not available XP Home, only in XP Pro). Once you do this and start and stop the Apache Service, you should be able to read and write to the repository directories. Note: Setting up a log in account for a Service can create a security hole. Consider your security requirements before doing this.
  • Individual files and directories that did not exist during the initial import, must be added individually using the svn add command.

의외로 많이. svn 명령때문에 시간을 먹기도 한다. 그래서 퍼옴.

출처 : http://www.abbeyworkshop.com/howto/misc/svn01/

'svn' 카테고리의 다른 글

[펌] svn 사용법  (0) 2006.01.17
svn 명령시 깨지는 글자나 나타날 때.  (0) 2005.11.01
svn freebook  (0) 2005.05.30
tip) 파일 하나만 Rep에서 가져오기  (0) 2005.05.30
TortoiseSVN  (0) 2005.05.30
Posted by '김용환'
,

Runtime

java core 2005. 6. 6. 10:30

Runtime 을 reflection으로 구해 실행시키는 코드

 

import java.util.*;
import java.io.*;
import java.lang.reflect.*;

public class RuntimeTest {

    Method exec;
    public RuntimeTest() throws NoSuchMethodException {
        exec = Runtime.class.getMethod("exec",
                new Class[] {String[].class, String[].class, File.class});
    }

    public Process exec() {
        String[] cmd = new String[] { "gcc", "-c",
            "a.c",
            "b.c"
        };
        String[] env = null;
        File workingDir = new File("./working/b");

        try {
            Object[] arguments = {cmd, env, workingDir};
            return (Process) exec.invoke(Runtime.getRuntime(),
                                                 arguments);
        } catch (InvocationTargetException exc) {
            exc.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) throws Exception  {
        RuntimeTest rt = new RuntimeTest();
        rt.exec();
    }
}

'java core' 카테고리의 다른 글

[펌] JVM 관련  (0) 2005.09.15
[TIP]자바 실행, 컴파일때 shell 이용법  (0) 2005.06.11
millisecond를 가지고 구하기  (0) 2005.05.13
java 5.0 enum.  (0) 2005.05.12
jdk 5.0 static import test code  (0) 2005.05.11
Posted by '김용환'
,

이클립스에 대해서 메모리를 확잡으면 된다.

 

이클립스 설치 디렉토리에 eclipse.ini 파일이 존재한다. 이 파일을 보면 다음과 같다.

 

-vmargs
-Xms64m
-Xmx256m

-Xms256m로 수정해보시라. 좀 빨리 뜬다. 요즘 swap하니라.. 일을 못 할 지경에 다다랐다. ㅜ.ㅜ

 

저게 머냐구 묻거든... 아래를 참조하시오.

 

관련자료.

http://www.instantiations.com/codepro/ws/docs/faq.html

 

How can I improve Application Developer/Eclipse startup time, reduce swapping and eliminate unnecessary garbage collection?
Begin by specifying the starting amount of memory (-vmargs -Xms###M) in your Application Developer/Eclipse startup command line (e.g., the target field within a Windows shortcut). If this is not specified, Eclipse's starting amount of memory is quite small (only 64 MB - see http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/java.html and scroll down until you find the -Xms option description), and can really slow down your startup and initial usage time. What happens with a small starting memory like 64 MB is that the java virtual machine (and thus Eclipse)...
  • tries to use currently allocated memory (initially 64MB)
  • garbage collects several times
  • finally determines that it really needs more memory
  • allocates more memory incrementally
  • repeats this process until it reaches a steady state

If you know you are going to use at least 128 MB or more, then try specifying -Xms128M or larger as the starting memory. It should improve your startup time and initial usage prior to swapping.

If you are certain that you are going to swap, then at least try to reduce the amount of time spent garbage collecting. For example, if Application Developer/Eclipse has to live within 256 MB, then it may be spending much of its time garbage collecting which appears like swapping (application freezes or slows to a crawl for a while). You might try specifying -Xmx384M as your maximum memory usage to reduce the amount of garbage collecting. Even if you have many large applications running simultaneously, you likely use one program for a while, then switch to another, then after a while switch back. The key here is that using a program for a while and ignoring the others, plus a larger maximum memory for java (like 384 MB) lets Application Developer/Eclipse take advantage of more physical memory and garbage collect less while the other programs are swapped out. Obviously there's a trade off of maximum memory for a program versus the physical memory available... you'll have to find that "sweet spot" somewhere between 256MB and 1GB for your machine.

Posted by '김용환'
,