Appendix A. A whirlwind tour of C#

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

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.

Using directive

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.

Classes

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:

  • privateCan be used only inside this class

  • protectedAccessible from this class and any subclasses

  • internalAccessible from any class in the current assembly

  • publicAvailable from any code

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:

  • abstractThis 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.)

  • staticThis 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.

  • sealedThis 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.

  • partialPartial 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

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.

Interfaces

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.

Enums

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

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

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.

Virtual and override methods

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.

Other method modifiers

A number of other modifiers can be included after the access modifier when defining a method. The more commonly seen modifiers are these:

  • staticThis 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#.

  • sealedThis override method can’t be overridden again in a subclass. This is often done for performance or security reasons.

  • abstractThis method must be implemented in subclasses. Abstract methods can’t contain any code.

Parameter passing

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.

Method overloading

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.

Delegates

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);

Events

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

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 and indexers

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.

Loops

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.

while loop

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.

do loop

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.

for loop

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 loop

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

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.

if

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.

switch

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.

try/catch/finally and throw

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.

lock

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.

new

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.

null

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 structs 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.

using statement

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.

Operators

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 not operator.

++ --

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 in Python) with reference types, although they can be overridden to be value equality. For value types, these are the same as the Python equivalents.

is

is

Corresponds to the Python isinstance function, although it can compare to only one type.

&& ||

logical and, logical or

Corresponds to Python and and or, and short-circuits in the same way. In the standard implementations the result is converted to bool, unlike Python’s Boolean operators.

??

null coalescing

The expression a ?? b returns a, or b if a is null. A common Python idiom is to use a or b for this purpose, which works because or doesn’t convert its result to a bool.

?:

conditional operator

The expression a ? b : c returns b if a is true, and c otherwise. It corresponds to the Python expression b if a else c.

Generics

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 strings, and then the compiler will ensure that only strings 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: classSpecifies 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: USpecifies 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.202.240