Can you create JavaScript like objects in C#
You can't. C# and JavaScript are different languages that solve different problems, consequently they have very different type-systems and approaches to data-modelling.
I've made this table which I hope indicates the fundamental differences between C# and JavaScript's type-systems. In particular, I want to draw your attention towards the important and fundamental differences in what "Properties" are and what object means.
|
C# |
JavaScript |
| Product types |
class or struct |
object instances |
| Type extension paradigm |
Single-inheritance (type inheritance) |
Prototype inheritance (value inheritance) |
Object foo = / object foo = is... |
An arbitrary reference to a nominatively-typed GC heap-allocated defined region in memory (including boxed values). |
A hashtable instance. |
| Properties are... |
Statically-defined getter/setter methods with-or-without backing fields. |
Hashtable entries with string keys and arbitrary values (including scalars, objects (other hashtables), and functions) |
class is... |
A statically-defined type in its entirety. |
Shorthand for a constructor function. |
| Type identity |
Statically defined. Type-identity is strictly nominative. |
(Generally) runtime-defined. Weak structural-typing. Type-identity is nominative when using constructor functions correctly (e.g. instanceof). TypeScript introduces strict structural typing. |
| Undefined type name |
Compiler error |
Runtime error. |
| Undefined type member dereference |
Compiler error |
Runtime error. |
I am wondering if it is possible to have a globally available object that behaves like objects in javascript?
- C# doesn't have globals.
- In C#, every value and method (function) must exist within a type definition (e.g.
class, struct, interface, etc).
- I suppose the closest equivalent is a value exposed via a
public static field or property, but unless the value's type is immutable you should not be manipulating it from C# program code because of thread-safety and concurrency issues. JavaScript doesn't have this problem because JavaScript is strictly single-threaded and does not allow for concurrent access to shared resources.
Can have some public properties with default values (preferably of any type, but at minimum strings)
You can define a type (i.e. a class) in C# with properties that have default values that can be overwritten by explicit values passed into the class's constructor. But you will still need to specify exact types for those properties.
If you want an arbitrary collection of values with named entries then use a Dictionary<K,V> instead. As Dictionary<K,V> is strongly-typed you'll need to use either V: Object (ewwwww) or define a custom union struct that explicitly and exhaustively covers all possible JavaScript-like values and Func and Action delegate-types for function-properties if you so wish.
Can be imported and be globally available in another module
- C# doesn't have "imports" or "modules".
- In C#, any
public or internal type in any .cs file can be used by any other .cs file in the same project.
- Though
internal types cannot be used by any other project.
- And you may need to add a
using YourProject.ChildNamespace to consuming files.
Default properties can be accessed from another module (and edited, if possible, but not necessary)
I'm unsure what you mean here, because "default properties" simply is not a thing in JavaScript nor C#.
I know that getting and updating properties requires defining "setters" and "getters", which is fine for the default ones, but how do I create new ones dynamically, from another module?
Again, "default" is not a meaningful term in this context in either language.
And C# is a strictly-typed, statically-typed language: you cannot add or remove members to types at runtime (at least as far as this conversation is concerned).
Remember that a JavaScript object is really just a view of a hashtable, so it sounds to me like you just need a Dictionary<K,V>, or even ExpandoObject (as @Matthew suggests) as your post implies that you don't want to take advantage of type-safety at all.
Is it possible to assign arbitrary types to this new property, without defining the type in the setter?
Not at runtime. Only statically (i.e. in a defined class which is compiled once at design-time). Note that the dynamic type in C# is really just an ExpandoObject behind-the-scenes, which is just another kind of dictionary/map/hashtable, just like JavaScript's object type.
I know that dictionaries can be used in C#, but i still have to specify the type
While you do-indeed have-to specify the TValue type when using Dictionary<TKey,TValue>, it shouldn't be a blocking problem though, as you can use any of the following types for TValue:
- Use
Object? (or Object if you're targeting .NET Framework 4.x) for TValue, but this is not type-safe and introduces too many awkward consequences for me to seriously consider.
- Using
dynamic (for TValue) has the same issues as using Object.
- Using
dynamic (instead of a Dictionary<TKey,TValue> entirely) is, just, no.
- Using a type-union (tip: use
OneOf<>) to define the exhaustive set of valid types. This can also be recursive.
Below is my first personal interpretation of your JavaScript example, using only statically defined and immutable types (as thread-safety is largely impossible without immutable data):
MyValues.cs:
using System;
namespace MyProject
{
public class MyValues
{
public String Prop1 { get; init; } = "somevalue";
public Boolean? Prop2 { get; init; } = null;
}
public class MyDerived : MyValues
{
public SomeOtherType NewProp { get; init; }
}
}
Program.cs:
using System;
namespace MyProject
{
public static class Program
{
public static void Main()
{
MyValues myValues1 = new MyValues()
{
Prop2 = false
};
Console.WriteLine( "Prop1: \"{0}\".", myValues1.Prop1 ); // "Prop1: "somevalue"."
Console.WriteLine( "Prop2: {0}.", myValues1.Prop2 ); // "Prop2: True."
//
MyDerived myValues2 = new MyDerived()
{
Prop1 = "newStringValue",
Prop2 = true,
NewProp = new SomeOtherType()
};
Console.WriteLine( "Prop1: \"{0}\".", myValues2.Prop1 ); // "Prop1: "newStringValue"."
Console.WriteLine( "Prop2: {0}.", myValues2.Prop2 ); // "Prop2: True."
}
}
}
Note that:
- The
Prop1 and Prop2 properties in class MyValues above wrap corresponding hidden private instance fields generated by the compiler. Prior to C# 3.0 every property declaration had to have an explicit backing field.
MyDerived inherits from MyValues (so it gains Prop1 and Prop2 implicitly), however this is just for demonstration purposes: in C# you should not use inheritance as a poor substitute for mixins and member-sharing. Only use inheritance when you're meaningfully implementing an "is" relationship and not a "has" relationship.
- Unfortunately even C# 10.0 (released in November 2021) still doesn't have true mixins and similarly its support for object-composition is very weak (let alone any kind of true algebratic types (ADTs)). While C# 11 will likely have simpler ADTs I'm not expecting support for true unions, products, and intersection-types until after C# 12 or even 13.
- Since C# 9.0, properties can be declared with
init-setters, which indicates that the property can only be set inside a constructor or inside an object-initializer. My example is using init-setters for simplicity, but they should be avoided in production-code except for truly optional values and never for any required values because the constructor cannot provide guarantees about object-initializers, as object-initializers run after the constructor has returned.
Below is my second personal interpretation of your JavaScript example, using dictionary collection-types with a OneOf<...> union to add type-safety.
Warning: I've been having issues getting global using to work in C# 10.0, especially with self-referential type-aliases. While my code below doesn't compile, it is still possible to convert type-aliases to struct wrappers that achieve the same effect.
Utility.cs:
namespace MyProject
{
// "Global Usings" imbue a type-alias with project-wide visibility, but requires C# 10.0.
// Also, this doesn't compile for me, but *in-principle* the code below is still correct.
global using ValueDict = Dictionary<String,PossibleValueTypes>;
global using PossibleValueTypes = OneOf<ValueDict,String,Int32,Boolean,Decimal,Null>;
global using ImmutablePossibleValueTypes = OneOf<ImmutableValueDict,String,Int32,Boolean,Decimal>;
global using ImmutableValueDict = ImmutableDictionary<String,PossibleValueTypes,Null>;
// This empty struct is used as a type-tag to allow `null` (which is a reference-value in C#/.NET) to be used _almost_ like a type (just like in TypeScript, where `null` is both a type and a value).
// But as C# is not JavaScript you really shouldn't ever do this (as C# has (almost) first-class support for nullable reference types already).
public struct Null {}
}
MyValues.cs:
using System;
using OneOf;
namespace MyProject
{
public static class MyValues
{
public static ImmutableValueDict Values { get; } = new ImmutableValueDict()
{
{ "Prop1", "someValue" },
{ "Prop2", new Null() },
};
}
}
Program.cs:
using System;
namespace MyProject
{
public static class Program
{
public static void Main()
{
ImmutableValueDict myValues1 = MyValues.Values;
Console.WriteLine( "Prop1: \"{0}\".", myValues1.Prop1 ); // "Prop1: "somevalue"."
Console.WriteLine( "Prop2: {0}.", myValues1.Prop2 ); // "Prop2: MyProject.Null"
//
ImmutableValueDict myValues2 = new ImmutableValueDict( myValues1 ) // <-- This constructor overload will copy the entries in `myValues1` into `myValues2` - though as `myValues2`'s collection-initializer overwrites both Prop1 and Prop2 it's kinda moot.
{
{ "Prop1", "newStringValue" ),
{ "Prop2", true ),
{ "NewProp", new ImmutableValueDict() ) // Nested dicts!
};
Console.WriteLine( "Prop1: \"{0}\".", myValues2.Prop1 ); // "Prop1: "newStringValue"."
Console.WriteLine( "Prop2: {0}.", myValues2.Prop2 ); // "Prop2: True."
}
}
}
dynamicandExpandoObject, but it's probably best to think of a solution that fits C# better rather than try and implement JavaScript idiomsdynamicisn't type-safe, andExpandoObjectis an implementation ofdynamic. I'm not a fan ofdynamicat all because it defeats the point of using a strongly-typed language in the first place.dynamic, a kitten dies...