Flylib.com

Books Software

 
 
 

Recipe3.25.Writing Code That Is Compatible with the Widest Range of Managed Languages


Recipe 3.25. Writing Code That Is Compatible with the Widest Range of Managed Languages

problem

You need to make sure your C# code will interoperate with all other managed languages that are CLS-compliant consumers, such as VB.NET.

Solution

Mark the assembly with the CLSCompliantAttribute :

[assembly: CLSCompliantAttribute(true)]

Discussion

By default, your C# assemblies created with VS.NET are not marked with the CLSCompliantAttribute . This does not mean that the assembly will not work in the managed environment. It means that this assembly may use elements that are not recognized by other CLScompliant languages. For example, unsigned numeric types are not recognized by all managed languages, but they can be used in the C# language. The problem occurs when C# returns an unsigned data type, such as uint , through either a return value or a parameter to a calling component in another language that does not recognize unsigned data typesVB.NET is one example.

CLS compliance is enforced only on types/members marked public or protected. This makes sense because components written in other languages will be able to use only the public or protected types/ members of components written in C#.


Marking your assembly as CLS-compliant means that any CLS-compliant language will be able to seamlessly interoperate with your code; that is, it enables CLS compliance checking. This makes it much easier on developers to catch problems before they manifest themselves , especially in an environment in which multiple managed languages are being used on a single project. Marking your entire assembly to be CLS-compliant is done with the following line of code:

[assembly: CLSCompliantAttribute(true)]

Sometimes you just can't be 100 percent CLS compliant, but you don't want to have to throw away the benefit of compiler checking for the 99.9 percent of your methods that are CLS compliant just so you can expose one method that is not. To mark these types or members as not being CLS compliant, use the following attribute:

[CLSCompliantAttribute(false)]

By passing a value of false to this constructor's isCompliant parameter, you prevent any type/member marked as such from causing compiler errors due to non-CLS-compliant code.

Many types/members in the FCL are not CLS compliant. This is not a problem when using C# to interact with the FCL. However, this is a problem for other languages. To solve this dilemma, the authors of the FCL usually included a CLS-compliant type/member where possible to mirror the non-CLS-compliant type/member.


{% if main.adsdop %}{% include 'adsenceinline.tpl' %}{% endif %}

The following is a list of some of the things that can be done to make code non-CLS-compliant when using the C# language:

  • Two identifiers with the same name that differ only by case

  • Using unsigned data types ( byte, ushort, uint, ulong )

  • Use of the UIntPtr type exposed through a public or protected member

  • Any public or protected member that exposes boxed value types

  • The use of operator overloading

  • An array of non-CLS-compliant types, such as unsigned data types

  • An enumeration type having a non-CLS-compliant underlying data type

See Also

See the "CLSCompliantAttribute Class" topic in the MSDN documentation.



Recipe 3.26. Building Cloneable Classes

problem

You need a method of performing a shallow cloning operation, a deep cloning operation, or both on a data type that may also reference other types.

Solution

Shallow copying means that the copied object's fields will reference the same objects as the original object. To allow shallow copying, add the following Clone method to your class:

using System;
	using System.Collections;
	using System.Collections.Generic;

	public class ShallowClone : ICloneable
	{
	    public int data = 1;
	    public List<string> listData = new List<string>();
	    public object objData = new object();

	    public object Clone()
	    {
	        return (this.MemberwiseClone());
	    }
	}

Deep copying or cloning means that the copied object's fields will reference new copies of the original object's fields. This method of copying is more time-consuming than the shallow copy. To allow deep copying, add the following Clone method to your class:

using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.Runtime.Serialization.Formatters.Binary;
	using System.IO;

	[Serializable]
	public class DeepClone : ICloneable
	{
	    public int data = 1;
	    public List<string> listData = new List<string>();
	    public object objData = new object();

	    public object Clone()
	    {
	        BinaryFormatter BF = new BinaryFormatter();
	        MemoryStream memStream = new MemoryStream();
	        BF.Serialize(memStream, this);
	        memStream.Position = 0;

	        return (BF.Deserialize(memStream));
	    }
	}

Add an overloaded Clone method to your class to allow for deep or shallow copying. This method allows you to decide at runtime how your object will be copied. The code might appear as follows :

using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.Runtime.Serialization.Formatters.Binary;
	using System.IO;

	[Serializable]
	public class MultiClone : ICloneable
	{
	    public int data = 1;
	    public List<string> listData = new List<string>();
	    public object objData = new object();

	    public object Clone(bool doDeepCopy)
	    {
	        if (doDeepCopy)
	        {
	            BinaryFormatter BF = new BinaryFormatter();
	            MemoryStream memStream = new MemoryStream();

	            BF.Serialize(memStream, this);
	            memStream.Position = 0;

	            return (BF.Deserialize(memStream));
	        }
	        else
	        {
	            return (this.MemberwiseClone());
	        }
	    }

	    public object Clone()
	    {
	        return (Clone(false));
	    }
	}

Discussion

Cloning is the ability to make an exact copy (a clone) of an instance of a type. Cloning may take one of two forms: a shallow copy or a deep copy. Shallow copying is relatively easy. It involves copying the object that the Clone method was called on.

The reference type fields in the original object are copied over, as are the value-type fields. This means that if the original object contains a field of type StreamWriter , for instance, the cloned object will point to this same instance of the original object's StreamWriter ; a new object is not created.

There is no need to deal with static fields when performing a cloning operation. There is only one memory location reserved for each static field per class, per application domain. Besides, the cloned object will have access to the same static fields as the original.


Support for shallow copying is implemented by the MemberwiseClone method of the Object class , which serves as the base class for all .NET classes. So the following code allows a shallow copy to be created and returned by the Clone method:

public object Clone( )
	{
	    return (this.MemberwiseClone( ));
	}

Making a deep copy is the second way of cloning an object. A deep copy will make a copy of the original object just as the shallow copy does. However, a deep copy will also make separate copies of each reference type field in the original object. Therefore, if the original object contains a StreamWriter type field, the cloned object will also contain a StreamWriter type field, but the cloned object's StreamWriter field will point to a new StreamWriter object, not the original object's StreamWriter object.

Support for deep copying is not automatically provided by the Clone method or the .NET Framework. Instead, the following code illustrates an easy way of implementing a deep copy:

BinaryFormatter BF = new BinaryFormatter( );
	MemoryStream memStream = new MemoryStream( );

	BF.Serialize(memStream, this);
	memStream.Flush( );
	memStream.Position = 0;

	return (BF.Deserialize(memStream));

Basically, the original object is serialized out to a memory stream using binary serialization, then it is deserialized into a new object, which is returned to the caller. Note that it is important to reposition the memory stream pointer back to the start of the stream before calling the Deserialize method; otherwise , an exception indicating that the serialized object contains no data will be thrown.

Performing a deep copy using object serialization allows the underlying object to be changed without having to modify the code that performs the deep copy. If you performed the deep copy by hand, you'd have to make a new instance of all the instance fields of the original object and copy them over to the cloned object. This is a tedious chore in and of itself. If a change is made to the fields of the object being cloned, the deep copy code must also change to reflect this modification. Using serialization, you rely on the serializer to dynamically find and serialize all fields contained in the object. If the object is modified, the serializer will still make a deep copy without any code modifications.

One reason you might want to do a deep copy by hand is that the serialization technique presented in this recipe works properly only when everything in your object is serializable. Of course, manual cloning doesn't always help there eithersome objects are just inherently noncloneable. Suppose you have a network management application in which an object represents a particular printer on your network. What's it supposed to do when you clone it? Fax a purchase order for a new printer?

One problem inherent with deep copying is performing a deep copy on a nested data structure with circular references. This recipe manages to make it possible to deal with circular references, although it's a tricky problem. So, in fact, you don't need to avoid circular references if you are using this recipe.

See Also

See the "ICloneable Interface" and "Object.MemberwiseClone Method" topics in the MSDN documentation.