C#
Basic C# for GH3d Reference in a Nutshell
1st edition author: Krzysztof Nazar contact: krzysztof.a.nazar@gmail.com
parametric support
C#
Basic C# for GH3d Reference in a Nutshell
1st edition author: Krzysztof Nazar contact: krzysztof.a.nazar@gmail.com
1. C# scripting component
C# scripting component can be found in Math tab in Script section. You can also place it on the canvas by double-clicking and typing „C#”. By default the component is created with ‘x’ and ‘y’ inputs and ‘out’ and ‘A’ outputs. „out” is a special output printing text messages about errors, warning and script behaviour. Names of the inputs and outputs (excluding „out”) become names of the variables inside a script. This means you have to follow C# naming rules when you rename them. After zooming in pluses and minuses appear which allow to add or remove inputs and outputs.
toggle script editor shrinking run script
variable names
Before you start scripting you have to specify input types by right clicking on the component. In addition, you have three ways of providing data (item, list, data tree). Visit http://4.rhino3d.com/5/rhinocommon/ for full reference of RhinoCommon types. Note: outputs types are always set to generic „object” type. the most generic base class
ways of providing data
RhinoCommon structures
RhinoCommon classes
2. Declaring variables In C# you declare a variable by providing type followed by variables name. The rules are: -it can’t be any of the reserved keywords, -it has to start with a letter or an underscore, -it can contain letters, underscores and numbers, -no whitespaces! A declared variable is a container for a certain type of data. Assigning a value for the first time is called initialization. data_type data_type data_type data_type
variable_name; variable_name = value; variable1, variable2, variable3; variable1 = value, variable2 = value;
3. Data types C# has some built in data types. Numeric types are divided into: 1. Signed integrals (positive and negative values): 2. Unsigned integrals (only positive values): 3. Real numbers:
sbyte, short, int, long
byte, ushort, uint, ulong float, double, decimal
These types differ with an amount of memory occupied and the method of number representation. There are also: char type
string type
which represents a single Unicode character (eg. „C”) which represents an immutable chain of char (eg „C# course”)
3b. Rhino and Grasshopper data types In Rhino there are some custom data types designed to represent geometry. Some of the examples are Point3d, Line, Poyline, Brep. Check out Rhino 5 SDK for full reference. Every RhinoCommon type has its „wrapper” in Grasshopper. Wrapping RhinoCommon objects in GH objects inside a script before outputting is a good practice which speeds up the program a lot. Point3d
GH_Point
Vector3d
GH_Vector
Curve
GH_Curve
Surface
GH_Surface
double
GH_Number
bool
GH_Boolean
RhinoCommon and Grasshopper type comparison
4. Punctuators and operators Puncuators help demarcate the structure of the program. Braces group multiple statements into a statement block and semicolons signalize the end of a single statement (you don’t need semicolons at the ends of statement blocks). { } ;
An operator transforms and combines expressions. Following is the list of operators commonly used: + * / %
addition subtraction multiplication division remainder after division
++ incrementation -- decrementation = assignment
+= -= *= /=
add and assign subtract and assign multiply and assign divide and assign
5. Boolean type C# bool type is a logical value that can be assigned either true or false. It is used to check conditions of script or program execution. bool myCondition; myCondition = true;
declaration and initialization of a bool type variable „myCondition”
If you want to compare numbers you have to use one of the following operators. All of them return bool type data. Notice the difference between an assignment(single ‘=’) and comparison(double ‘=’) operators! == equal != not equal && conditional AND || conditional OR ! conditional NOT
< smaller than <= smaller or equal to > larger than >= larger or equal to
6. Comments Comments are lines of code ommited by compiler / interpreter. It is a good practice to explain complex parts of the code. There are two types of comments: single and multiline. //single line comment starts with double forward slashes //here you can write whatever you want /* multi line comment starts with a forward slash and an asterix. You can write whatever you want but you have to end the commentary block with an asterix and a forward slash: */
7. if() {...} else {...} instruction You can control your program ďŹ&#x201A;ow by using if() else instructions. if (some_condition) { //doSomething; } else { //doSomethingElse; }
if() checks if the condition (bool type) is true or false and executes one of the statement blocks
bool itRains = false; bool itIsSunny = true; bool itIsWindy = false; if (!itIsWindy && (itRains || itIsSunny )) { TakeAnUmbrella(); } else if (itIsWindy && itRains) { TakeACoat(); } else { StayAtHome(); }
If it is not windy and it rains or it is sunny, take an umbrella. Else, if it is windy and it rains, take a coat. Else stay at home. In this case computer (or a robot) will take an umbrella.
8. switch() { ... } instruction If you have to check multiple conditions, it is convenient to use switch() instruction. It checks if provided variable is equal to speciďŹ ed cases. switch(some_variable) { case value1: doSomething(); break; case value2: doSomethingElse(); break; case value3: doSomethingElse2(); break; default: doSomethingElse3(); break; }
Is equivalent to:
if (some_variable == value1) doSomething(); else if (some_variable == value2) doSomethingElse(); else if (some_variable == value3) doSomethingElse2(); else doSomethingElse3();
9. for() loop Sometimes you want to do things in a loop. The most popular is a for() loop. This is the syntax: for(initial_statement; condition; action_after_every_pass) { //instructions here }
The most common approach is to use initial statement for declaring and initializing an indexing variable (usually ‘i’, ‘j’, ‘k’, ‘l’, ‘m’, ‘n’) with 0, set the condition to „indexing variable smaller than number of passes” and end every pass with incrementation of the indexing variable (eg. „i++;”) for(int i=0; i<count; i++) { Print(i.ToString()); }
This loop would print values from zero to count-1
It is possible to prematurely end a loop with break; instruction or to jump to the next pass with continue; for(int i=0; i<count; i++) { if (i % 2 != 0) continue; //jump to next pass, i++ if (i == 10) break; //exit loop }
Print(i.ToString());
This loop would print only even values from zero to eight (at ten loop exits). If count is lower than 10, then the loop will finish earlier.
for() loop is not starting if the condition is not met at the beginning.
10. while() loop while(condition) { //do something }
This is simpler version of a loop. It executes until the conditions are met and is not starting if they are not. You can use break; and continue; like in a for loop. int number=1; while(number < 10) { //instructions here Print(number.ToString()); number += 2; }
This loop would print only uneven numbers from one to nine (at ten loop exits)
11. do {...} while(); loop do {
//do something } while(condition);
The only difference between while() and do ... while() loops is the latter executes at least once no matter if the condition is met or not. Notice the semicolon after while(). int number = -10; do { Print(number.ToString()); number--; } while(number < 0);
This loop would print „-10”
12. foreach() loop foreach (type a_variable in a_list_or_array) { //do something with a_variable }
foreach() loop is used to iterate through an array or a list. int[] numbers = new int[]{1, 2, 4, 6, 7}; foreach (int num in numbers) { Print(num.ToString()); }
This loop would print: 1 2 4 6 7
13a. Arrays type[] arrayName = new type[numberOfElements];
An array represents a fixed number of variables (elements) of a particular type. They are always stored in a contiguous block of memory what provides very efficient access. You need to declare how many elements will an array have. You access a specific element in an array by providing its index in square brackets after the array’s name. Indexing starts from 0! int[] numbers = new int[50]; for(int i=0; i<numbers.Length; i++) { numbers[i] = i; } int[] numbers2 = new int[]{1, 2, 4, 6, 7};
Two ways of initializing an array: 1) creating an array with 50 elements of default value 2) creating an array and specifying all of the elements inside braces
You can create a multidimensional array by declaring number of elements in more than one dimension. You access a speciďŹ c element by providing multiple indices separated by a comma. int[,] numbers = new int[10, 20]; for(int i=0; i<numbers.GetLength(0); i++) { for(int j=0; j<numbers.GetLength(1); i++) { numbers[i, j] = i*j; } }
Creates an array with 10 rows and 20 colums
13b. Lists List<type> listName = new List<type>();
Lists are similar to arrays but can have varying number of elements. You can add and remove data from a list. And once again: indexing starts from 0!
List<int> numbers = new List<int>(); for(int i=0; i<10; i++) { numbers.Add(i); }
Two ways of initializing a list: 1) creating an empty list and ďŹ lling it with new elements 2) creating a list and specifying the elements inside braces
List<int> numbers2 = new List<int>(){1, 2, 4, 6, 7}; for(int i=numbers.Count-1; i>0; i--) { if(numbers[i] % 2 == 0) numbers.RemoveAt(i); }
Removing every even number from the list. Notice iterating from the end towards the beginning.
14a. Methods <modifiers> returning_type method_name(parameters) { // a set of instructions }
A method performs some action in a series of instructions. It can receive some data from the caller by parameters and output data back by specifying a return type. A method can specify void return type, which indicates that itâ&#x20AC;&#x2122;s not returning any value to the caller. It is possible to output more than one variable (list, array, data tree) via ref/out parameters.
public void PrintHello() { Print(”Hello”); }
A non-returning method
public void PrintNumber(int num) { Print(num.ToString()); }
A non-returning method with single parameter
public int TwoTimes(int num); { return num*2; }
A method returning single integer number
//... //inside a Runscript() method: int number1 = 4; int number2 = 0; PrintHello(); PrintNumber(number1); number2 = TwoTimes(number1); PrintNumber(number2); //...
Results: Hello 4 8
Methods allow, inter alia, the following modifiers: Static modifier static Access modifiers public internal private protected Inheritance modifiers new virtual abstract override sealed
14b. Method overloading It is possible to have multiple methods with the same name but different implementation and parameter list. Consider the following example: public void PrintNumber(int intNum) { Print(intNum.ToString()); } public void PrintNumber(double realNum) { Print(realNum.ToString()); } public void PrintNumber(double realNum, string label) { Print(label + „: ” + realNum.ToString()); } //... //inside a Runscript() method: int number1 = 2; double number2 = 3.14; string label = „number2”; PrintNumber(number1); PrintNumber(number2); PrintNumber(number2, label); //...
Results: 2 3.14 number2: 3.14
Overloaded PrintNumber() method allows for two types of parameters: int or double. This gives more elasticity. It is not possible to overload methods by changing only the return type. Signatures of PrintNumber() methods: PrintNumber(int) PrintNumber(double) PrintNumber(double, string)
14c. Out and ref modifiers It is possible to return more than one value to a caller. By default arguments are passed by value, what means that a copy of object is created inside a method and destroyed after returning. You can however pass by reference which means that an alias of passed object is created. This makes all changes persistent. public void Iterate(int intNum) { intNum += 1; Print(”inside Iterate(): ” + intNum.ToString()); } public void IterateRef(ref int intNum) { intNum += 1; Print(”inside IterateRef(): ” + intNum.ToString()); }
Results: 2 inside Iterate(): 3 2 inside IterateRef(): 3 3
//... //inside a Runscript() method: int myNumber = 2; Print(myNumber.ToString()); Iterate(myNumber); Print(myNumber.ToString());
Iterate() iterated only a local copy of myNumber. IterateRef() iterated the original myNumber variable through a reference
IterateRef(ref myNumber); Print(myNumber.ToString()); //...
An out argument is like ref, except it need not be assigned before being passed into a method and has to be assigned before it comes out of the method. Notice you have to write ref or out both when you define and call a method.
15a. Value and reference types. There are four categories of data types in C#: values, references, generic type parameters and pointers. In this section we’ll focus on the first two. Value types comprise most built-in types (all numeric types, char and bool) and custom struct and enum types. Reference types comprise all class, array, delegate and interface types (including predefined string type).. The fundamental difference is how these types are stored in computer memory.
public struct MyPoint2d_Struct { public double X; public double Y; } public class MyPoint2d_Class { public double X; public double Y; }
Value type
Reference type
//... //inside a Runscript() method: MyPoint2d_Struct point1 = new MyPoint2d_Struct(); point1.X = 10; point1.Y = 20;
Declaration and initialization of a custom-type object Accessing a member with . operator
MyPoint2d_Struct point2 = point1; point2.X = 15; Print(point1.X.ToString()); Print(point1.Y.ToString()); Print(point2.X.ToString()); Print(point2.Y.ToString()); //...
// // // //
Copying object
10 20 15 20
In case of value types, assignment operator creates an actual independent copy of an object. point1 wasn’t affected by changes made on point2.
MyPoint2d_Struct instance:
}
X Y
values
point1:
point2:
10
15
20
20
memory independent instances
//... //inside a Runscript() method: MyPoint2d_Class point1 = new MyPoint2d_Class(); point1.X = 10; point1.Y = 20;
Declaration and initialization of a custom-type object Accessing a member with . operator
MyPoint2d_Class point2 = point1; point2.X = 15; Print(point1.X.ToString()); Print(point1.Y.ToString()); Print(point2.X.ToString()); Print(point2.Y.ToString()); //...
// // // //
Copying a reference to an object!
15 20 15 20
In case of reference types, assignment operator creates a copy of reference to data. Reference is in fact a variable storing a memory adress. Data referenced by point1 was affected by changes made on point2, because they are pointing to the same place in memory!.
MyPoint2d_Class instance: Reference
X Y
}
point1 data point2
15 20
}
data
references to the same adress in the memory
15b. Creating custom types: enum This is the simplest custom type. Its name comes from „enumerable”. It lets you specify a group of named numeric constants. Each enum member has an underlying integral value. These values are by default of type int and are set to 0, 1, 2... in the declaration order. public enum BoxSide { Front, Back, Left, Right, Top, Bottom, Invalid = -1 }
//0 //1 //2 //3 //4 //5 //explicitly defined -1
//... //inside a Runscript() method: BoxSide mySide = BoxSide.Top;
Results: We’re on top! 4
Note: you can cast enum to its underlying type.
if(mySide == BoxSide.Top) Print(”We’re on top!”); Print(((int)mySide).ToString()); //...
15c. Creating custom types: class Every program written in C# consists of classes. Classes are object descriptions. They can contain information about what an object is and how it behaves. Fields, properties and methods are called class members. The following is a classic example of a class. Reminder: classes are reference types! public class Point { public double X; public double Y; } //... //inside a Runscript() method: Point myPoint = new Point(); myPoint.X = 10; myPoint.Y = 15.5; //...
This class contains two members (fields) which are public (can be accessed from the outside of class) and of type double.
In order to create an object instance you have to declare a reference by typing: <object_type> name_of_your_choice; After this you create an actual object in memory by typing: new <object_type>() and assign it to previously created reference You can access object’s members by typing reference’s name followed by a dot ( . ) operator and a member’s name.
More complex classes may have the following: before the class keyword:
attributes and class modifiers: public, internal, abstract, sealed, static, unsafe and partial
following the class Name:
generic type parameters, a base class and interfaces
Within the braces: class members:
methods, properties, indexers, events, fields, constructors, overloaded operators, nested types and a finalizer
A class usually contains a lot of different fields and methods. Let’s add some functionality to the Point class. public class Point { private double x; private double y; public void SetCoordinates(double newX, double newY) { x = newX; y = newY; Note: }
}
naming convention is that private fields start with a lowercase and public fields start with an uppercase
public double GetX(){return x;} public double GetY(){return y;}
//... //inside a Runscript() method: Point myPoint = new Point(); myPoint.X = 10; //Error! X is private and can be accessed //only from the inside of Point class //...
Notice that now both x and y are private (can be accessed only from the inside of class). To make it possible to set and read coordinates we defined three public methods. This approach is reasonable if you want to prevent the user from inputting strange values (then a check inside the setting method is needed). //... //inside a Runscript() method: Point myPoint = new Point(); myPoint.SetCoordinates(10, 15.3); Print(myPoint.GetX().ToString()); Print(myPoint.GetY().ToString()); //...
Results: 10 15.3
15d. Classes: properties Although having Set... and Get... methods might be useful, it is unhandy for user to call them every time to make changes or read values. It is also not intuitive. Look at this code: //... //inside a Runscript() method: Point myPoint = new Point(); myPoint.SetX(10); if(myPoint.GetX() > 20) Print(”We’re so far!”); //...
Same member is accessed with different methods.
There is a way to combine restricted only-method access with convenience of public fields. It is about using properties. public class Point { private double _x; //a private field... private double _y; //a private field... public double X { //a public property... set {if(value>=0) _x = value; else _x = 0;} get {return _x;} } public double Y { //a public property... set {if(value>=0) _y = value; else _y = 0;} get {return Y;} } }
Properties resemble methods but don’t define any arguments. The returning type is the type of an underlying field (which has to be explicitly defined by us, but can in case of auto-properties it is implicitly defined by a compiler). Inside the braces two behaviours are described: set, which accepts an implicit argument denoted with keyword value and get which has to return some data to caller. Now it is possible to use myPoint as follows: //... //inside a Runscript() method: Point myPoint = new Point(); myPoint.X = 10; myPoint.Y = -10; //”set” checks if provided value is smaller //than 0 and assigns Y a default value of 0. Print(myPoint.X.ToString()); Print(myPoint.Y.ToString()); //...
//10 //0
If you don’t want to specify any special behaviour you can just restrict setting or getting a value by the user. This type of a member is called an automatic property and the most common use is to make „setting” private (controlled by the class) and „getting” being public. public class Point { public double X { get; private set; } public double Y { get; private set; } }
Underlying methods are implicitly defined by a compiler.
15e. Classes: constructors public class class_name { public class_name(optional_arguments) { constructor’s instructions... } }
When an object is created in memory, a special method called a constructor is executed. It usually initializes all fields and properties of an object and thus allows to set initial state. Constructors are non-returning, public (in general) and have the same name as the class. If you don’t define any constructor, then a default parameterless, empty constructor is implicitly created by the compiler. public class Point { public Point(double initX, double initY){ X = initX; Y = initY; }
}
public double X { get; private set; } public double Y { get; private set; }
//... //inside a Runscript() method: Point myPoint = new Point(15, 16.2); Print(myPoint.X.ToString()); Print(myPoint.Y.ToString()); //...
//15 //16.2
Note: constructors can be (and very often are) overloaded just as regular methods
What if a constructor accepts arguments that are named exactly as the object’s fields? No problem, just use this keyword which denotes the object itself. public class Point { public Point(double X, double Y){ this.X = X; this.Y = Y; }
}
public double X { get; private set; } public double Y { get; private set; }
15f. Classes: inheritance public class class_name : base_class { }
Inheritance is one of the most important aspects of every object-oriented programming (OOP) language. It is used for code reuse and extensibility.
public class ColouredPoint : Point { public System.Drawing.Color ColourRGB { get; set; } } //... //inside a Runscript() method: ColouredPoint myColouredPoint = new myColouredPoint(); myColouredPoint.X = 3.14; myColouredPoint.Y = 10.5; myColouredPoint.ColourRGB = Color.FromArgb(0,0,0); Print(myPoint.X.ToString()); //3.14 Print(myPoint.Y.ToString()); //10.5 Print(myPoint.ColourRGB .ToString()); //Color [A=255, R=0, G=0, B=0] //...
ColouredPoint inherits all members of its base class Point. When a ColouredPoint object is created, a default base class constructor is called first. It is possible to reuse some base code like this: public class ColouredPoint : Point { public ColouredPoint(double x, double y, int r, int g, int b) : base(x, y) { ColourRGB = Color.FromArgb(r, g, b); } }
public System.Drawing.Color ColourRGB { get; set; }
Calling the base class constructor with provided arguments
15g. Classes: overriding base methods When a class is derived (inherits) from a class which has some virtual (or override) methods, it can override their implementation. As every object is implicitly derived from System.Object class which has a public virtual string ToString() method, we can override it in our Point class. public class Point { public Point(double initX, double initY){ X = initX; Y = initY; }
}
public double X { get; private set; } public double Y { get; private set; } public override string ToString() { return string.Format(”X: {0}, Y: {1}”, X, Y); }
//... //inside a Runscript() method: Point myPoint = new Point(15, 16.2); Print(myPoint.ToString()); //...
//X: 15, Y: 16.2
15g. Static members A static modifier makes a member common to a whole class instead of being specific for a single instance. Different from regular members, you access static members by typing class name followed by a dot operator and static member’s name. public class Point { public Point(double initX, double initY){ X = initX; Y = initY; PointCount++; } public static int PointCount = 0; public double X { get; private set; } public double Y { get; private set; } public override string ToString() { return string.Format(”X: {0}, Y: {1}”, X, } } //... //inside a Runscript() method: Point myPoint1 = new Point(15, 16.2); Point myPoint2 = new Point(-3, 12); Point myPoint3 = new Point(5, -7.8); Print(Point.PointCount.ToString()); //3 //...
Note: A whole class can be marked as static. If so, then it can contain only static members. A very good example is System.Math class containing many useful methods and values.
Y);
Accessing a static member by typing class’ name, a dot operator and a member’s name
16. DataTree<> class DataTree<> class is designed for handling branched data you know from Grasshopper. Consider the following example:
(C) (D)
(A) (B)
1. A list of two items is created
(E)
2. A list of two items is branched
3. A data tree is branched
In fact, the resulting data structure is a list of lists which are marked with „branching info”. We have the following: List<int> {2, 1};
with branching info {0;0;0;0}
(made from the first item(C) made from the first item(A))
List<int> {2};
with branching info {0;0;0;1}
(made from the second item(D) made from the first item(A))
List<int> {2, 1};
with branching info {0;0;1;0}
(made from the first item(E) made from the second item(B))
This data tree would be created like this in C#: DataTree<int> myTree = new DataTree<int>(); myTree.AddRange(new List<int>(){2, 1}, new GH_Path(0,0,0,0)); myTree.AddRange(new List<int>(){2}, new GH_Path(0,0,0,1)); myTree.AddRange(new List<int>(){2, 1}, new GH_Path(0,0,1,0));
It’s that simple! When components try to combine data trees, they match lists by GH_Paths. Here’s how you iterate through a DataTree: DataTree<int> myTree = new DataTree<int>(); myTree.AddRange(new List<int>(){2, 1}, new GH_Path(0,0,0,0)); myTree.AddRange(new List<int>(){2}, new GH_Path(0,0,0,1)); myTree.AddRange(new List<int>(){2, 1}, new GH_Path(0,0,1,0)); for(int i=0; i<myTree.BranchCount; i++) { for(int j=0; j<myTree.Branch(i).Count; j++) { Print(myTree.Branch(i)[j].ToString()); } }
Property returning a number of branches Method returning a branch as a list Accessing elements of returned list