Obviously this book has primarily been about IronPython, but in several places we’ve explored some aspects of using IronPython with other .NET languages, the foremost being C#. If you don’t know C#, this appendix will give you a brief overview of the core parts of the language. It should be enough to let you read the code examples in the book, although you’ll probably want more detail if you’re trying to write C# code.
Namespaces are similar to Python packages and modules. The classes in an assembly live in namespaces, which can be nested to provide a hierarchical structure. This structure is used to group together related classes to make them easier to find. Namespaces are specified in C# like so:
namespace EvilGenius.Minions { class GiantRobot { ... } }
Here we declare a namespace called EvilGenius.Minions
, which contains the GiantRobot
class. Another major feature of namespaces is that they enable us to avoid name collisions: the fully qualified name of the class is EvilGenius.Minions.GiantRobot
, and we could happily use other GiantRobot
classes in our project as long as they are in different namespaces.
This is similar to how Python modules work, but namespaces differ from modules in several ways. First, namespaces are entirely independent of the name of the file in which they are defined, and you can have a file that defines multiple namespaces. Conversely, you can define classes in a particular namespace in multiple different files. For example, if the GiantRobot
class is defined in GiantRobot.cs
, you could have another file defining the fully qualified class EvilGenius.Minions.Corrupt-Bureaucrat
. Both classes are in the EvilGenius.Minions
namespace.
Namespaces and assemblies are orthogonal; assemblies are the physical organization of code, whereas namespaces provide logical organization.
In C# code, you have access to any classes in the current assembly or referenced assemblies. Classes in the current namespace can be referred to directly. To refer to classes in other namespaces, you can use fully qualified class names in your code, but that’s very verbose. The alternative is to add a using
directive to the top of the file:
using EvilGenius.Minions;
Then occurrences of the unadorned class name GiantRobot
in the code refer to EvilGenius.Minions.GiantRobot
.
In the case of a name collision (say you also wanted to use Transformers.Autobots.GiantRobot
in the code), the using
directive can create an alias:
using autobots=Transformers.Autobots;
Then you can refer to the alternative GiantRobot
class as autobots.GiantRobot
.
Class declarations in C# are structured like this:
public class GiantRobot: Robot, IMinion, IDriveable { public string name; public GiantRobot(string name) { this.name = name; } // more fields and methods here }
The access modifier public
before the class
keyword indicates that the class is accessible outside the current namespace. The names after the colon specify what this class inherits from. GiantRobot
is a subclass of the Robot
class, as well as the IMinion
and IDriveable
interfaces. C# doesn’t allow multiple inheritance of classes, but implementing multiple interfaces is fine. If no parent class is specified in a class definition (or it inherits only from interfaces), the class inherits from System.Object
.
The body of the class can contain definitions of various kinds of members:
Constructors
Fields (like the name
attribute in the previous snippet)
Methods
Properties
Events
Operators
Each of these members has access modifiers as well:
Classes can also contain other types (including classes, interfaces, structs, or delegates) as members.
As well as access modifiers, other modifiers can be applied to classes when they are defined:
abstract
—. This declares that the class can be used only as a base class and can’t be instantiated. It might contain declarations of abstract methods (without any implementation) that subclasses must define when they are declared. (Abstract classes can still contain concrete implementations of some methods.)
static
—. This indicates that this class can’t be instantiated or used as a type (so it can’t be subclassed). Static classes are essentially containers for their members; in Python it would make more sense to use a module.
sealed
—. This prevents this class from being subclassed, for security or performance reasons. When the compiler sees a method call on a sealed class, it can generate a direct call to the method, rather than a virtual call.
partial
—. Partial classes are classes that are defined in more than one file. This can be useful if some code in a class is machine generated; the generated code can be kept in a separate file and regenerated without clobbering the custom code.
Attributes are declarative information that can be attached to different elements of a program and examined at runtime using reflection. They can be applied to types, methods, and parameters, as well as other items. The .NET framework defines a number of attributes, such as SerializableAttribute
, STAThreadAttribute
, and FlagsAttribute
, which control various aspects of how the code runs. You can also create new attributes (to be interpreted by your own code) by inheriting from System.Attribute
.
The following code applies the SecretOverrideAttribute
to the OrbitalWeatherControlLaser.Zap
method:
class OrbitalWeatherControlLaser { //... [SecretOverride("zz9-plural-z-alpha")] public void Zap(Coords target) { //... } }
As you can see, attribute constructors can take arguments, and the –Attribute
suffix of the class name can be omitted when attaching it.
An interface is a type that defines methods and properties with no behavior. Its purpose is to make a protocol explicit without imposing any other restrictions on the implementation. For example, the previous GiantRobot
class implements the IDriveable
interface:
interface IDriveable { IDriver Driver {get; set;} void Drive(); }
This means that GiantRobot
must provide a definition of the Driver
property and the Drive
method. Classes that implement multiple interfaces must provide definitions for all of the interfaces’ members. Interfaces can subclass other interfaces (and even inherit from multiple interfaces), which means that they include all of the members of their bases as well as the members they declare.
Ordinarily, a class that inherits from an interface can implement its members simply by defining them with the correct name, parameters, and return type. In some cases, you might not want the interface members to be part of your class’s interface (for example, if you have two different interfaces that clash). You can implement those members explicitly, so that they are available only when used through a reference to the interface:
public class ExplicitlyDriveable: IDriveable { // member prefixed with interface name public void IDriveable.Drive() { // implementation here... } // other members }
Then code trying to use the Drive
method on an ExplicitlyDriveable
instance must cast it to IDriveable
first.
Enumerations are collections of named constants, which look like this:
enum RobotColor { MetallicRed, MetallicGreen, MetallicBlue, Black }
The enum values can be referred to in code using RobotColor.MetallicBlue
. By default, enumerations inherit from int
, but they can be declared (using the inheritance syntax) as any of the integral .NET types: sbyte
, byte
, short
, ushort
, int
, uint
, long
, or ulong
.
Enums can be combined as flags with the bitwise-or operator (|
) if you annotate them with the Flags
attribute, in which case you should also specify the value of each member (by default, they’re assigned sequentially from 0). For example, if you wanted the RobotColor
values to be combinable, you could define it as follows:
[Flags] enum RobotColor { MetallicRed = 1, MetalicGreen = 2, MetallicBlue = 4, Black = 8 }
Structs are data structures that are defined similarly to classes. The difference is that a struct is a value type rather than a reference type like a class. When a variable is of a class type, it contains a reference to an instance of the class (or null
). A struct variable directly contains the members of the struct rather than being a reference to an object elsewhere in memory. This is generally useful for performance or memory optimization: a large array of structs can be much quicker to create than the same size array of objects. In some situations using structs can be slower, though, particularly when assigning or passing them as parameters (because all their member data needs to be copied).
Struct definitions look like this:
struct Projectile { public float speed; public Projectile(float speed) { //... } }
Structs can’t inherit from any type (other than object
, which they inherit from implicitly), and no types can inherit from them.
Methods in C# are members of classes and structs that are defined by giving the access level, the return type, the name of the method, and the parameters it accepts, as well as the block of code that is executed when it is called:
class GiantRobot { //... public void GoToTarget(string targetName) { Point location = this.targetDatabase.Find(targetName); this.FlyToLocation(location); } }
The void
return type indicates that this method returns nothing.
The virtual
modifier applied to a method indicates that it can be overridden in a subclass. The subclass’s version of the method must have the same visibility, name, parameter types, and return type as the base class method and be defined using the keyword override
.
class GiantRobot { //... public virtual void TravelTo(Point destination) { this.TurnTowards(destination); while (this.Location != destination) { this.Trudge(); } } } class FlyingRobot: GiantRobot { //... public override void TravelTo(Point destination) { if (this.WingState == WingState.Operational) { this.FlyTo(destination); // whee! } else { base.TravelTo(destination); // oh well... } } }
(Methods defined with override
can also themselves be overridden by subclasses.)
In a method, this
refers to the current instance in the same way as self
in Python, although this
doesn’t need to be a parameter of each method. To call a superclass method or property (for example, from the body of an override
method), you refer to base
, which works similarly to calling super(Class, self)
in Python, although the semantics are simpler because any C# class has only one superclass.
A number of other modifiers can be included after the access modifier when defining a method. The more commonly seen modifiers are these:
static
—. This method must be called directly on the class rather than on an instance (and so can’t refer to this
). Often code that would be in a standalone function in Python is put into a static method in C#.
sealed
—. This override
method can’t be overridden again in a subclass. This is often done for performance or security reasons.
abstract
—. This method must be implemented in subclasses. Abstract methods can’t contain any code.
By default, method parameters are passed by object value in the same way as in Python (with the extra wrinkle that structs are copied). In Python, this is the only way to pass parameters; if you want to see modifications by a function or method, you need to mutate an object that is passed in.
In C# you can change this behavior using the ref
and out
parameter modifiers. If a parameter has the ref
modifier, it is passed by reference instead. Assignments to that parameter in the method will be reflected in the variable passed in by the caller. For example, an int
variable passed as a ref
parameter can be assigned in the method, and the variable’s value will be updated in the calling scope. Out
parameters are similar, with the difference that they don’t need to be assigned a value before calling the method, and they must be assigned in the method. The difference between them is that out
parameters are viewed as extra return values of the method (for which you might use a tuple in Python), while ref
parameters are values that are both input and potentially output of the method.
There can be multiple different methods in a class with the same name, as long as the combination of name and parameter types is unique within the class. Methods that have the same name but different parameters are said to be overloaded. When you call an overloaded method, the compiler uses the compile-time types of the parameters passed in to determine which version of the method is called. This is an important distinction between overload dispatch, where the selection of which overload to call is purely based on the declared types of the parameters, and method dispatch, where the method called is determined by the type of the instance at runtime, regardless of the declared type of the variable.
A delegate is a type that represents a reference to a method. Delegates are essentially the same as Python function references; a delegate that has been created from an instance carries a reference back to that instance in the same way as a bound method reference. Delegate definitions look like this:
delegate int IntegerFunction(int a, int b);
This creates an IntegerFunction
type that accepts two integers and returns an integer.
In addition to creating delegates from method references, you can create them from anonymous functions, which are very similar to Python lambdas:
IntegerFunction f = (int a, int b) => (a * a + b + 1);
An event is a member of a class that enables it to notify other code when something happens. Each event is declared with a delegate type, and interested parties add delegates of that type to the event; the delegates will be called when the event is triggered. For example, the HideoutEntrance
might need to let other parts of the system know when there’s an intruder:
delegate bool SecurityHandler(string details); class HideoutEntrance { //... event SecurityHandler IntruderDetected; }
Then interested parties can create SecurityHandler
delegates and attach them to the IntruderDetected
event so that they can respond to the problem:
hideoutEntrance.IntruderDetected += this.ActivateSentryTurret;
Event handlers can be removed with the -=
operator. Events are triggered by calling them, in the same way as invoking a delegate (although if no handlers are attached, the event will be null
).
Operator overloading enables you to define how instances of your class should behave when they’re used in expressions. For example, if you want to be able to add instances together, you can define operator +
:
class Magnitude { //... public static operator +(Magnitude a, Magnitude b) { return Magnitude(a.size + b.size); } }
These operator overrides work in much the same way as the magic methods in Python (such as __add__
, __mul__
, or __eq__
). Operator members must be declared as public static
, and they can be for unary operators (like ++
) as well as binary operators (like addition).
Properties are function members that are run when a particular attribute is retrieved or assigned. They can be used for validating the value that’s being set or lazily creating an expensive attribute only when it’s needed. A property definition looks like this:
class Heist { //... private float executionTime; public float Hours { get { return this.executionTime / 3600; } set { this.executionTime = value * 3600; } } }
A property can be made read-only by leaving out the setter or made write-only by omitting the getter. The setter is called with an implicit parameter named value
, which, surprisingly enough, is the value that was set.
Indexers are members that allow a class to act like a collection, similarly to the Python __getitem__
and __setitem__
magic methods. They can have getters and setters in the same way as properties, but they are defined slightly differently:
class LootBag { // container for items private ArrayList _underlyingItems; //... public object this[string name] { get { return this._underlyingItems[name]; } set { this._underlyingItems[name] = value; } } }
Indexers can be overloaded if you want to handle different types or numbers of parameters. In the same way as for properties, the value to be set is an extra implied parameter called value
, and either the getter or setter can be omitted to make the indexers read- or write-only.
C# has four looping constructs: while
, do
, for
, and foreach
. while
and foreach
are like the while
and for
loops in Python; the do
and for
loops don’t have any Python analogue.
The while
loop works in almost exactly the same way as its Python counterpart. The only major difference is that the condition of the loop in C# must evaluate to a bool
, while Python will allow any type.
int a = 10; while (a > 0) { a--; }
The loop control keywords break
and continue
work (for all C# loops) exactly the same way as they do in Python. The condition of the loop must be included in parentheses.
The do
loop is like a while
loop, where the condition is checked at the end of the loop body; that is, the loop body always executes at least once.
int a = 0; do { a--; } while (a > 0);
After the execution of this loop, a
will have the value -1.
The C# for
loop has three parts in addition to the loop body:
An initialization clause, which sets up variables before the loop begins
A condition that is tested before each execution of the loop body
An iteration clause, which is executed after each execution of the loop body
Here’s an example:
int total = 0; for (int i = 0; i < 10; i++) { total += i; }
After execution, total
will have the value 45 (the sum of 0 to 9), because it stops when i
equals 1. Each of the three control clauses is optional, and each clause can consist of multiple statements separated by commas.
foreach
loops are the closest equivalent to Python’s for
loops. The foreach
loop can iterate over any collection that implements the IEnumerable
interface, including arrays.
string[] names = new string[] {"Alice", "Bob", "Mallory"}; foreach (string person in names} { Console.WriteLine(person); }
Casts are used to convert an expression to a different type. There are two kinds of casts in C#:
GiantRobot robot = new GiantRobot(); IDriveable vehicle = (IDriveable)robot; IMinion minion = (robot as IMinion);
The difference between these casts is that the first form, (IDriveable)robot
, will raise an exception at runtime if the conversion is invalid, while the robot as IMinion
expression will return null
if there is no way to convert the type.
Casting from a class to one of its base classes or interfaces (upcasting) always succeeds. Downcasting (going in the opposite direction) can fail, because not every IMinion
object is a GiantRobot
.
The C# if
statement looks very similar to the Python one. The condition must be in parentheses and yield a Boolean.
if (!robot.AtDestination()) { robot.Walk(); } else if (robot.CanSee(target)) { robot.Destroy(target); } else { robot.ChillOut(); }
If
statements can be chained as you see here, so there’s no need for the elif
from the Python if
statement.
Some situations that would require an if-elif-else
construct or a dictionary lookup in Python can be done more cleanly in C# using a switch
statement.
WeaponSelection weapon; switch (target.Type) { case TargetType.Building: weapon = WeaponSelection.FistsOfDoom; break; case TargetType.ParkedCar: weapon = WeaponSelection.Stomp; break; case TargetType.FluffyKitten: case TargetType.FluffyBunny: weapon = WeaponSelection.MarshmallowCannon; break; case default: // death ray not operational yet weapon = WeaponSelection.InconvenienceRay; break; }
The case labels must be constants, and each case has to end with a flow-control statement like break
, throw
, or return
; execution isn’t allowed to fall through from one case to another. If you want fall-through behavior, you can use the goto case
statement to explicitly jump to the case that should be executed. Two case labels together (like the FluffyKitten
and FluffyBunny
case shown previously) are treated as if they share the same body. If the switch
expression doesn’t match any of the case labels and there’s a default case, it is executed.
Exceptions in C# are handled in the same way as they are in Python, with only minor syntactic differences:
try { robot.ReturnToBase(TransportMode.FootThrusters); } catch (FootThrusterFailure) { robot.ReturnToBase(TransportMode.BoringWalking); } catch (EmergencyOverrideException e) { robot.SelectTarget(e.NewTarget); } catch { robot.AssumeDefensivePosition(); throw; // re-throw the original exception } finally { robot.NotifyBase(); }
In this example, catch
is equivalent to Python’s except
, and throw
corresponds to raise
.
All reference types in C# can be used as locks to prevent multiple threads from occupying critical sections at the same time. The lock
statement provides a convenient way to specify a critical section:
lock(this.RightEye) { this.RightEye.Close(); this.RightEye.Open(); }
This will prevent any other thread from executing this section of code with the same RightEye
object. As long as other operations on the right eye are locked, it will also prevent this wink from being interrupted or interrupting some other operation.
The lock
statement guarantees that the lock will be released when execution leaves the block, even if an exception is thrown.
The new
operator is used to create new instances of types. It can be used to create class or struct instances, arrays, and delegates.
GiantRobot robot = new GiantRobot("destructo", true);
For classes and structs, the constructor that matches the parameters is called.
The new
operator can also initialize the object that is created, by assigning to members (if the object is a class or struct instance) or by specifying the elements for a collection or array.
string[] demands = new string[] {"helicopter", "money", "island"};
When creating an array, if initial items have been provided, you don’t need to also specify the size.
Where Python has None
, a value that is the singleton instance of NoneType
, the equivalent in C# is null
. null
is a reference that (paradoxically) doesn’t refer to any object. This has two consequences: first, null
can be assigned to a variable of any reference type, and second, value types like int
, DateTime
, or custom struct
s can’t be null
, because they don’t contain references.
The second fact can be inconvenient in some situations, so each value type in C# has a nullable version, represented with the type name followed by a question mark. For example, a nullable integer variable would be declared with int?
. Nullable types can then be used as normal (although there are performance differences under the hood) and be assigned null
in the same way as reference types.
The using
statement (not to be confused with the using
directive) ensures that an object will be disposed of when execution leaves the block. It’s essentially a specialized version of a try
-finally
statement.
using (DatabaseConnection conn = context.OpenConnection()) { // use the opened connection } // the connection will be Disposed at the end of the block
The target object must implement the IDisposable
interface, which has only one member, the Dispose
method. The using
statement guarantees that Dispose
will be called on the target, regardless of whether execution leaves the block by returning, throwing an exception, or falling off the bottom.
There’s a large overlap between the operators in Python and C#. Table A.1 lists those that are different in C#:
Table A.1. Differences between C# and Python operators
Operator | Name | Description |
---|---|---|
| logical negation | The equivalent of Python’s |
| increment and decrement | Adds or subtracts 1 from a value. These are unusual from a Python perspective, because they both yield a value and mutate the operand, which can be confusing. Increment and decrement can be prefix or postfix; if prefix, the increment/decrement is done before yielding the value; if it is postfix, the increment or decrement is done after yielding the value. |
| equality and inequality | Generally used for identity (reference equality, |
| is | Corresponds to the Python |
| logical and, logical or | Corresponds to Python |
| null coalescing | The expression |
| conditional operator | The expression |
Generic types are types that include one or more type parameters. They’re often used to create containers or data structures that can operate on a range of types of objects without having to cast them to some common base type (in the most general case, this would be object
). When you create an instance of a generic type, you specify the type parameters, and the instance acts as if those types had replaced all of the type parameters in the definition.
class LinkedList<T> { private T item; private LinkedList<T> rest; public LinkedList(T item) { this.item = item; this.rest = null; } public T Head { get { return this.item; } } public void InsertAfter(T newItem) { LinkedList<T> newNode = new LinkedList<T>(newItem); newNode.rest = this.rest; this.rest = newNode; } // more list methods... }
This code (partially) defines a LinkedList
generic class that could hold any object. The difference between this and an ArrayList
or Python list (which could also hold any type of object) is that client code can create a LinkedList
of string
s, and then the compiler will ensure that only string
s go in and come out.
LinkedList<string> list = new LinkedList<string>("abc"); list.InsertAfter("def"); string s = list.Head // no casting is required.
The LinkedList
class doesn’t need to do anything with the type parameter T
, other than store instances of T
or pass them around. Some generic classes need to have more interaction with their type parameters, for example, being able to create instances or call methods on the objects. To guarantee that the actual type used with the generic class has the necessary methods, generic classes can specify constraints on the type parameters, such as these:
where T: class
—. Specifies that the type specified for T
must be a reference type, while where T: struct
constrains T
to be a value type
where T: <class or interface name>
—. Says that T
must inherit from the class or interface
where T: new()
—. Requires that T
has a no-argument
constructor
where T: U
—. Specifies that T
must be the same type or a subclass of the U
type parameter
Any type constraints are checked at compile time when an instance of a generic type is created.