How can I use c# attributes? - c#

I have to create a pdf report from several classes that are going to contains several properties.
I need to display the value of the propertie and a label in front of it.
Something like :
detailsCalcul :
Numero client : valueOfMyProperty.
...
I was thinking of doing something like this :
[NomRapport("detailsCalcul")]
public class MyClass
{
[NomChamp("Numero client")]
public string NumeroClient { get; set; }
}
I sucessfully acessed to the value of my two attributes :
System.Reflection.MemberInfo[] proprietes = typeof(MyClass).GetMembers();
MyClass client = new MyClass();
client.NumeroClient = "1234";
foreach (var p in proprietes)
{
var aa = p.GetCustomAttributes(true);
for (int i = 0; i < aa.Length; i++)
{
var test = aa[i];
if (test.GetType() == typeof(NomChampAttribute))
{
var nomChamp = ((NomChampAttribute)attributes[i]).ToString());
}
}
}
i would like to know if it is possible access to the value of my property while I am acessing to the attribute ?
Thanks for your help,
Guillaume

An attribute does not know the context to which it is applied; you cannot even get to the property, let alone the instance. However, if you have a PropertyInfo and an instance, then:
object value = property.GetValue(instance, null);

Related

Design time serialization of custom type property

So basically I have a custom UserControl containing a private array of Label objects and I want to be able to access exclusively their Text properties from the outside.
I therefore added a property which type LabelTextCollection is an implementation of IEnumerable and has my Label array as its inner list. Furthermore, I added an implementation of UITypeEditor to allow editing from the windows forms designer.
To try it out, I added my control in a form and edited the property's value. All of that works fine until I close and reopen the designer and the labels take back their default values.
After looking around it seems I have to add an implementation of CodeDomSerializer to allow my type to succesfully serialize into the {Form}.Designer.cs file at design time. I tried serializing a comment line first to test it out but no code is generated.
My final goal would be to have a line like
this.{controlName}.Titles.FromArray(new string[] { "Whatever" } )
added at design time after the property was modified using my editor.
What am I misunderstanding and/or doing wrong ?
Custom Type
[DesignerSerializer(typeof(LabelTextCollectionSerializer), typeof(CodeDomSerializer))]
public class LabelTextCollection : IEnumerable<string>, IEnumerable
{
private Label[] labels;
public LabelTextCollection(Label[] labels)
{
this.labels = labels;
}
public void SetLabels(Label[] labels)
{
this.labels = labels;
}
public IEnumerator<string> GetEnumerator()
{
return new LabelTextEnum(labels);
}
IEnumerator IEnumerable.GetEnumerator()
{
return new LabelTextEnum(labels);
}
public string this[int index]
{
get { return labels[index].Text; }
set { labels[index].Text = value; }
}
public override string ToString()
{
if (labels.Length == 0) return string.Empty;
else
{
StringBuilder sb = new StringBuilder("{ ");
foreach (string label in this)
{
sb.Append(label);
if (label == this.Last()) sb.Append(" }");
else sb.Append(", ");
}
return sb.ToString();
}
}
public string[] ToArray()
{
string[] arr = new string[labels.Length];
for (int i = 0; i < labels.Length; i++) arr[i] = labels[i].Text;
return arr;
}
public void FromArray(string[] arr)
{
for(int i = 0; i < arr.Length; i++)
{
if (i >= labels.Length) break;
else labels[i].Text = arr[i];
}
}
public class LabelTextEnum : IEnumerator<string>, IEnumerator
{
private readonly Label[] labels;
private int position = -1;
public LabelTextEnum(Label[] labels)
{
this.labels = labels;
}
public object Current
{
get
{
try
{
return labels[position].Text;
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
string IEnumerator<string>.Current { get { return (string)Current; } }
public void Dispose()
{
return;
}
public bool MoveNext()
{
return ++position < labels.Length;
}
public void Reset()
{
position = -1;
}
}
}
Type Editor
public class LabelTextCollectionEditor : UITypeEditor
{
IWindowsFormsEditorService _service;
IComponentChangeService _changeService;
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
if (provider != null)
{
_service = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
_changeService = (IComponentChangeService)provider.GetService(typeof(IComponentChangeService));
if (_service != null && _changeService != null && value is LabelTextCollection)
{
LabelTextCollection property = (LabelTextCollection)value;
LabelTextCollectionForm form = new LabelTextCollectionForm() { Items = property.ToArray() };
if (_service.ShowDialog(form) == DialogResult.OK)
{
property.FromArray(form.Items);
value = property;
_changeService.OnComponentChanged(value, null, null, null);
}
}
}
return value;
}
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
}
Serializer
public class LabelTextCollectionSerializer : CodeDomSerializer
{
public override object Serialize(IDesignerSerializationManager manager, object value)
{
var baseSerializer = (CodeDomSerializer)manager.GetSerializer( typeof(LabelTextCollection).BaseType, typeof(CodeDomSerializer));
object codeObject = baseSerializer.Serialize(manager, value);
if (codeObject is CodeStatementCollection && value is LabelTextCollection)
{
var col = value as LabelTextCollection;
var statements = (CodeStatementCollection)codeObject;
statements.Add(new CodeCommentStatement("LabelTextCollection : " + col.ToString()));
}
return codeObject;
}
}
Property of custom Type
[Category("Appearance")]
[Editor(typeof(LabelTextCollectionEditor), typeof(UITypeEditor))]
public LabelTextCollection Titles { get; }
EDIT :
I added a set to my Titles property and set up my project for design-time debugging, I then realized that an exception was thrown on the line
object codeObject = baseSerializer.Serialize(manager, value);
stating that the Label type isn't marked as [Serializable].
I'm assuming that the base serializer is trying to write a call to my LabelTextCollection constructor and to serialize the labels field as a parameter of it.
I tried replacing the line with
object codeObject = new CodeObject();
which got rid of the exception but didn't write anything in the designer.cs file.
I'm (once again) assuming that nothing is happening because there is no relation between the CodeObject I just created and the file (unless that relation is established after it's returned by the Serialize method ?).
As you can probably tell, I'm pretty new regarding the CodeDom stuff so how should I create this object properly ?
EDIT 2 :
I'm so dumb... I forgot the codeObject is CodeStatementCollection test...
So the comment line is writing fine, now all I need to do is to write the correct line with CodeDom and it should work fine.
If someone wants to help, I currently have added to the designer.cs file :
this.FromArray( new string[] { "TEST" } );
So I'm missing the control's and the property's names to get to my final goal.
I'll answer my own post to recapitulate what I did to fix it when that's done.
I managed to make the serialization work as I intended so I'm going to recap what I changed from the code I originally posted.
First my property of custom type needed a set to be able to be modified by the editor.
[Editor(typeof(LabelTextCollectionEditor), typeof(UITypeEditor))]
public LabelTextCollection Titles { get; set; }
I wrongly assumed that the property's value was changing because the label's texts were effectively changing in the designer after using the editor.
That was happening because the editor could access the reference to the inner label array through the use of the LabelTextCollection.FromArray method.
With the setter, the property is now properly edited at design-time.
The rest of the changes are all in the serializer so i'm posting the whole updated code :
public class LabelTextCollectionSerializer : CodeDomSerializer
{
public override object Serialize(IDesignerSerializationManager manager, object value)
{
CodeStatementCollection codeObject = new CodeStatementCollection();
if (value is LabelTextCollection)
{
LabelTextCollection col = value as LabelTextCollection;
// Building the new string[] {} statement with the labels' texts as parameters
CodeExpression[] strings = new CodeExpression[col.Count()];
for (int i = 0; i < col.Count(); i++) strings[i] = new CodePrimitiveExpression(col[i]);
CodeArrayCreateExpression arrayCreation = new CodeArrayCreateExpression(typeof(string[]), strings);
// Building the call to the FromArray method of the currently serializing LabelTextCollection instance
ExpressionContext context = manager.Context.Current as ExpressionContext;
CodeMethodInvokeExpression methodInvoke = new CodeMethodInvokeExpression(context.Expression, "FromArray", arrayCreation);
codeObject.Add(methodInvoke);
}
return codeObject;
}
}
To recap the changes I made in that class :
Removed the call to the baseSerializer.Serialize method to manage the whole serialization myself
Initializing the codeObject variable as a new CodeStatementCollection
Building my call to the LabelTextCollection.FromArray method using CodeDom
All of that now successfully writes the line I wanted in the Designer.cs file.
PS :
Thanks to #TnTinMn for the help and the push in the right direction.
EDIT :
After thorough testing of the serializer, I realized that the labels' texts went back to their default value when rebuilding the assembly containing the LabeltextCollection type while having a design view of a form containing my custom control opened.
The reason for that was that the property of LabeltextCollection type could not be serialized because the condition value is LabelTextCollection was false in that case as there was a discrepancy between two LabelTextCollection types from different assembly versions.
To fix that, I removed any direct reference to the type and accessed the method I needed to call through the Type class.
That got me the following serializer code :
public class LabelTextCollectionSerializer : CodeDomSerializer
{
public override object Serialize(IDesignerSerializationManager manager, object value)
{
CodeStatementCollection codeObject = new CodeStatementCollection();
// Building the new string[] {} statement with the labels' texts as parameters
string[] texts = value.GetType().GetMethod("ToArray").Invoke(value, null) as string[];
CodeExpression[] strings = new CodeExpression[texts.Length];
for (int i = 0; i < texts.Length; i++) strings[i] = new CodePrimitiveExpression(texts[i]);
CodeArrayCreateExpression arrayCreation = new CodeArrayCreateExpression(typeof(string[]), strings);
// Building the call to the FromArray method of the currently serializing LabelTextCollection instance
ExpressionContext context = manager.Context.Current as ExpressionContext;
CodeMethodInvokeExpression methodInvoke = new CodeMethodInvokeExpression(context.Expression, "FromArray", arrayCreation);
codeObject.Add(methodInvoke);
return codeObject;
}
}
You could still test the type of value using Type.Name but as my serializer only manages a single type, that wasn't needed in my case.

Attribute values do not reflect changes made at runtime

I have a problem and can't wrap my mind around it:
I made a mathode which should set propertie values of an specific attribute "MappingAttribute" and return the new object.
The problem:
The attribute values are always set to the default value "false".
Where am I wrong?
static public T MapToClass<T>(SqlDataReader reader) where T : class
{
T returnedObject = Activator.CreateInstance<T>();
PropertyInfo[] modelProperties = returnedObject.GetType().GetProperties();
for (int i = 0; i < modelProperties.Length; i++)
{
MappingAttribute[] attributes = modelProperties[i].GetCustomAttributes<MappingAttribute>(true).ToArray();
if (attributes.Length > 0) {
attributes[0].AutoIncrement = true;
attributes[0].Primekey = true;
}
}
return returnedObject;
}
Attributes are not stored anywhere except in the assembly metadata. They are only materialized into attribute instances when asked to do so by reflection - in your case via GetCustomAttributes<MappingAttribute>. But: you then discard these. The next time GetCustomAttributes<MappingAttribute> is called, fresh new instances will be handed out, with the values from the assembly metadata.
Basically: updating properties of attribute instances does not mean that other code will see those changes when that code asks about attribute metadata.
To illustrate this:
using System;
class FooAttribute : Attribute { public string Value { get; set; } }
[Foo(Value = "abc")]
class Bar
{
static void Main()
{
var attrib = (FooAttribute)typeof(Bar)
.GetCustomAttributes(typeof(FooAttribute), false)[0];
Console.WriteLine(attrib.Value); // "abc"
attrib.Value = "def";
Console.WriteLine(attrib.Value); // "def"
// now re-fetch
attrib = (FooAttribute)typeof(Bar)
.GetCustomAttributes(typeof(FooAttribute), false)[0];
Console.WriteLine(attrib.Value); // "abc"
}
}

Why is my game serializing this class?

So I'm making a game, and it saves users' progress on the computer in a binary file. The User class stores a few things:
Integers for stat values (Serializable)
Strings for the Username and the skin assets
Lists of both the Achievement class and the InventoryItem class, which I have created myself.
Here are the User fields:
public string Username = "";
// ID is used for local identification, as usernames can be changed.
public int ID;
public int Coins = 0;
public List<Achievement> AchievementsCompleted = new List<Achievement>();
public List<InventoryItem> Inventory = new List<InventoryItem>();
public List<string> Skins = new List<string>();
public string CurrentSkinAsset { get; set; }
The Achievement class stores ints, bools, and strings, which are all serializable. The InventoryItem class stores its name (a string) and an InventoryAction, which is a delegate that is called when the item is used.
These are the Achievement class's fields:
public int ID = 0;
public string Name = "";
public bool Earned = false;
public string Description = "";
public string Image;
public AchievmentDifficulty Difficulty;
public int CoinsOnCompletion = 0;
public AchievementMethod OnCompletion;
public AchievementCriteria CompletionCriteria;
public bool Completed = false;
And here are the fields for the InventoryItem class:
InventoryAction actionWhenUsed;
public string Name;
public string AssetName;
The source of the InventoryAction variables are in my XNAGame class. What I mean by this is that the XNAGame class has a method called "UseSword()" or whatever, which it passes into the InventoryItem class. Previously, the methods were stored in the Game1 class, but the Game class, which Game1 inherits from, is not serializable, and there's no way for me to control that. This is why I have an XNAGame class.
I get an error when trying to serialize: "The 'SpriteFont' class is not marked as serializable", or something like that. Well, there is a SpriteFont object in my XNAGame class, and some quick tests showed that this is the source of the issue. Well, I have no control over whether or not the SpriteFont class is Serializable.
Why is the game doing this? Why must all the fields in the XNAGame class be serializable, when all I need is a few methods?
Keep in mind when answering that I'm 13, and may not understand all the terms you're using. If you need any code samples, I'll be glad to provide them for you. Thanks in advance!
EDIT: One solution I have thought of is to store the InventoryAction delegates in a Dictionary, except that this will be a pain and isn't very good programming practice. If this is the only way, I'll accept it, though (Honestly at this point I think this is the best solution).
EDIT 2: Here's the code for the User.Serialize method (I know what I'm doing in inefficient, and I should use a database, blah, blah, blah. I'm fine with what I'm doing now, so bear with me.):
FileStream fileStream = null;
List<User> users;
BinaryFormatter binaryFormatter = new BinaryFormatter();
try
{
if (File.Exists(FILE_PATH) && !IsFileLocked(FILE_PATH))
{
fileStream = File.Open(FILE_PATH, FileMode.Open);
users = (List<User>)binaryFormatter.Deserialize(fileStream);
}
else
{
fileStream = File.Create(FILE_PATH);
users = new List<User>();
}
for (int i = 0; i < users.Count; i++)
{
if (users[i].ID == this.ID)
{
users.Remove(users[i]);
}
}
foreach (Achievement a in AchievementsCompleted)
{
if (a.CompletionCriteria != null)
{
a.CompletionCriteria = null;
}
if (a.OnCompletion != null)
{
a.OnCompletion = null;
}
}
users.Add(this);
fileStream.Position = 0;
binaryFormatter.Serialize(fileStream, users);
You cannot serialize a SpriteFont by design, actually this is possible (.XNB file) but it hasn't been made public.
Solution:
Strip it off your serialized class.
Alternatives:
If for some reasons you must serialize some font, the first thing that comes to my mind would be to roll-out your own font system such as BMFont but that's a daunting task since you'll have to use it everywhere else where you might already do ...
Generate a pre-defined amount of fonts (i.e. Arial/Times/Courier at size 10/11/12 etc ...) using XNA Content app (can't recall its exact name); then store this user preference as two strings. With a string.Format(...) you should be able to load the right font back quite easily.
Alternative 2 is certainly the easiest and won't take more than a few minutes to roll-out.
EDIT
Basically, instead of saving a delegate I do the following:
inventory items have their own type
each type name is de/serialized accordingly
their logic does not happen in the main game class anymore
you don't have to manually match item type / action method
So while you'll end up with more classes, you have concerns separated and you can keep your main loop clean and relatively generic.
Code:
public static class Demo
{
public static void DemoCode()
{
// create new profile
var profile = new UserProfile
{
Name = "Bill",
Gold = 1000000,
Achievements = new List<Achievement>(new[]
{
Achievement.Warrior
}),
Inventory = new Inventory(new[]
{
new FireSpell()
})
};
// save it
using (var stream = File.Create("profile.bin"))
{
var formatter = new BinaryFormatter();
formatter.Serialize(stream, profile);
}
// load it
using (var stream = File.OpenRead("profile.bin"))
{
var formatter = new BinaryFormatter();
var deserialize = formatter.Deserialize(stream);
var userProfile = (UserProfile) deserialize;
// set everything on fire :)
var fireSpell = userProfile.Inventory.Items.OfType<FireSpell>().FirstOrDefault();
if (fireSpell != null) fireSpell.Execute("whatever");
}
}
}
[Serializable]
public sealed class UserProfile
{
public string Name { get; set; }
public int Gold { get; set; }
public List<Achievement> Achievements { get; set; }
public Inventory Inventory { get; set; }
}
public enum Achievement
{
Warrior
}
[Serializable]
public sealed class Inventory : ISerializable
{
public Inventory() // for serialization
{
}
public Inventory(SerializationInfo info, StreamingContext context) // for serialization
{
var value = (string) info.GetValue("Items", typeof(string));
var strings = value.Split(';');
var items = strings.Select(s =>
{
var type = Type.GetType(s);
if (type == null) throw new ArgumentNullException(nameof(type));
var instance = Activator.CreateInstance(type);
var item = instance as InventoryItem;
return item;
}).ToArray();
Items = new List<InventoryItem>(items);
}
public Inventory(IEnumerable<InventoryItem> items)
{
if (items == null) throw new ArgumentNullException(nameof(items));
Items = new List<InventoryItem>(items);
}
public List<InventoryItem> Items { get; }
#region ISerializable Members
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
var strings = Items.Select(s => s.GetType().AssemblyQualifiedName).ToArray();
var value = string.Join(";", strings);
info.AddValue("Items", value);
}
#endregion
}
public abstract class InventoryItem
{
public abstract void Execute(params object[] objects);
}
public abstract class Spell : InventoryItem
{
}
public sealed class FireSpell : Spell
{
public override void Execute(params object[] objects)
{
// using 'params object[]' a simple and generic way to pass things if any, i.e.
// var world = objects[0];
// var strength = objects[1];
// now do something with these !
}
}
Okay, so I figured it out.
The best solution was to use a Dictionary in the XNAGame class, which stores two things: an ItemType (an enumeration), and an InventoryAction. Basically, when I use an item, I check it's type and then look up it's method. Thanks to everyone who tried, and I'm sorry if the question was confusing.

Listen for object changes from the outside in C#

I have an object which I would like to monitor in case its data changes. However I am not allowed to add additional events or interfaces like INotifyPropertyChanged to the object, so I need to purely watch it from the outside and see if it has changed in character and the go through all it's properties to identify what has changed.
So I basically need a FileSystemWatcher, but for an object.
Does C# offer any functionality I am looking for here?
You'll have to brute-force it and do something like capturing the state of the object and then look for differences when you need to detect changes (either through periodic polling or at specific points where you need to know the diffs).
This, I believe, is what a number of systems that monitor for changes in data model objects do (e.g. RavenDB).
I made a quick and dirty console app that roughly does what you are wanting. It should be enough to give you an idea but will need a whole lotta work if you want to use it in production code. :)
class Program
{
static void Main(string[] args)
{
DemoClass demo = new DemoClass { IntValue = 1, StringValue = "asdf" };
var watcher = new DemoClassWatcher();
watcher.Capture(demo);
demo.StringValue = "1234";
var results = watcher.Compare(demo); // results = "StringValue"
demo.IntValue = 1234;
results = watcher.Compare(demo); // results = "StringValue", "IntValue"
watcher.Capture(demo);
results = watcher.Compare(demo); // results = empty
}
}
public class DemoClass
{
public string StringValue { get; set; }
public int IntValue { get; set; }
}
public class DemoClassWatcher
{
private DemoClass lastRecorded = null;
public void Capture(DemoClass objectToWatch)
{
lastRecorded = new DemoClass()
{
IntValue = objectToWatch.IntValue,
StringValue = objectToWatch.StringValue
};
}
public List<string> Compare(DemoClass currentObject)
{
var changes = new List<string>();
var props = typeof(DemoClass).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var propertyInfo in props)
{
var currentVal = propertyInfo.GetValue(currentObject);
var prevVal = propertyInfo.GetValue(lastRecorded);
if (currentVal is IComparable)
{
if ((currentVal as IComparable).CompareTo(prevVal) != 0)
changes.Add(propertyInfo.Name);
continue;
}
throw new NotSupportedException("Properties must support IComparable until someone fixes me up");
}
return changes;
}
}
You can't listen for events that are not there, but you can poll the states and compare them to their previous state.
Pseudocode:
///initial config:
yourObject = getAReferenceToTheObject();
previousState = new ObjectClass(yourObject);
///somewhere in a thread/loop
if (previousState.SomeProperty!= yourObject.SomeProperty){
//state changed for SomeProperty on yourObject
}
//TODO: check other properties
previousState = new ObjectClass(yourObject); //implement copy constructor

Dynamic property assigned to object

Is it possible to attach dynamic property to an object of user-defined class?
public class Room
{
public int NumberOfDoors { get; set; }
public int NumberOfWindows { get; set; }
}
then from other context:
Room room = new Room();
dynamic contact = new ExpandoObject();
contact.NumberOfWalls = 4;
and then somehow associate NumberOfWalls with room, as its property?
Update (Larger Picture):
as per #nawfal's suggestion
I have a cached List<Room> being iterated in a razor view (outside the themes folder), calling a
particular partial view (from the current theme) for each element. One of the theme needs an extra
property in Room. I only have access to modify code in that particular theme folder, the partial
views cshtml files (don't ask why).
So its basically:
(psuedocode)
Room.NoOfWalls = SomeHeavyLiftingProcess(Room.NoOfWindows, Room.NoOfDoors)
I am looking for a way o update the List<Room> rooms object with NoOfWalls in
HttpRuntime.Cache["rooms"] to avoid calling SomeHeavyLiftingProcess() with each request. The
goal is to inject a property in cached object. Unfortuntely HttpRuntime.Cache["rooms"] is object
type and doesn't allow me to do this:
HttpRuntime.Cache["rooms"][3]["NoOfWalls"] = SomeHeavyLiftingProcess(..)
So I am thinking, for the first request (when cache is empty or invalid):
Unpackig: Retrieve (List<Room>)HttpRuntime.Cache["room"], inject NoOfWalls in the current room object.
Repacking: Update List<Room> room with the new object and assign it back to HttpRuntime.Cache.
For the subsequent requests, the value of NoOfWalls will come from cached object #Model.NoOfWalls.
You cannot add properties not defined in a class to an existing instance, without using a dynamic object like ExpandoObject.
If you need to add members to an existing class, you can create a child class with a special constructor:
public class SpecialRoom : Room
{
public SpecialRoom() { }
public SpecialRoom(Room copy)
{
this.NumberOfDoors = copy.NumberOfDoors;
this.NumberOfWindows = copy.NumberOfWindows;
}
public int NumberOfJacuzzis { get; set; }
}
Usage:
var room = new Room();
room.NumberOfDoors = 3;
var specialRoom = new SpecialRoom(room)
{
NumberOfJacuzzis = 7
};
Or:
var listOfRooms = new List<Room>();
// ...
var listOfSpecialRooms = listOfRooms.Select(x => new SpecialRoom(x));
listOfSpecialRooms.ForEach(x => x.NumberOfJacuzzis = ComplexCalculation(x));
If you have an existing concrete object (like an instance of the Room class), you can convert it to a dynamic object with a method like this:
public static dynamic ConvertObjectToDynamic(object value)
{
if (value == null)
{
return null;
}
IDictionary<string, object> dynamicObject = new ExpandoObject();
var properties = value.GetType().GetProperties(
BindingFlags.Public | BindingFlags.Instance);
foreach (var propertyInfo in properties)
{
if (propertyInfo.GetIndexParameters().Length == 0)
{
var propertyValue = propertyInfo.GetValue(value);
dynamicObject[propertyInfo.Name] = propertyValue;
}
}
return dynamicObject;
}
Usage:
var room = new Room();
room.NumberOfDoors = 3;
dynamic dynamicObject = ConvertToDynamic(room);
dynamicObject.WhateverYouWant = 7;
Now dynamicObject.NumberOfDoors will be 3, and dynamicObject.WhateverYouWant will be 7.

Categories

Resources