Having issues understanding class constructors in F# - c#

I have the following code:
module ExchangeSocket =
type Socket(url, id, key) =
let Communicator = new TestCommunicator(url)
let Client = new WebsocketClient(Communicator)
do
Communicator.Name <- "test"
Communicator.ReconnectTimeoutMs <- int (TimeSpan.FromSeconds(30.).TotalMilliseconds)
if we look at the last two lines, the C# usage is like that:
Communicator = new WebsocketCommunicator(wsUrl) { Name = "tst", ReconnectTimeoutMs = (int) TimeSpan.FromSeconds(30).TotalMilliseconds };
Now I read that to make a class constructor, I have to use the 'new' keyword; so I make the fields members and do a 'new' section:
member this.communicator : TestCommunicator
member this.client : WebsocketClient
new() =
this.communicator <- new TestCommunicator(url)
this.client <- new WebsocketClient(this.communicator)
but this doesn't work (line 15 is the top line in this example)
Socket.fs(15, 64): [FS0010] Incomplete structured construct at or before this point in member definition. Expected 'with', '=' or other token.
my questions are:
How to make this work?
What does 'new' bring that 'do' doesn't?

Now I read that to make a class constructor, I have to use the 'new' keyword
To make additional class constructors you should use the new keyword. The primary constructor is sufficient for you here.
In member this.communicator : TestCommunicator, while you have specified the type of the property communicator, you have not specified how to get (or set) this property. It is missing = ... or with get/set as the error message says.
new() =
this.communicator <- new TestCommunicator(url)
this.client <- new WebsocketClient(this.communicator)
When you fix the previous error, you will get another error here because 1. new() should return a Socket, and 2. it references this and url, which are not in scope.
Probably what you want is:
type Socket(url, id, key) =
let communicator =
new TestCommunicator(url,
Name = "test",
ReconnectTimeoutMs = int (TimeSpan.FromSeconds(30.).TotalMilliseconds))
let client = new WebsocketClient(communicator)
member _.Communicator = communicator
member _.Client = client
While this answer does what you asked for, better would be to read the spec for classes ( See https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/classes and https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/members/) and some examples of usage. Your code suggests you were guessing which is not the right approach.

Related

C# method syntax similar to object initializer

We have an extension method that accepts an action to initialize an object.
Is there some way to improve the syntax of such a call:
public static T NewRow<T>(this IUow uow, Action<T> action();
// this does internally call
public T NewRow<T>(Action<T> initializer) where T : IBo
{
T bo = NewRow<T>();
initializer.Invoke(bo);
return bo;
}
uow.NewRow<ICustomer>(customer => {
customer.Name = "Zzz";
customer.Info = "Abc"
);
I thought maybe I could use something similar to the object initializer syntax?
uow.NewRow<ICustomer>({
Name: "Zzz",
Info: "Abc"
});
The idea is to get rid of customer.* = ... in every line.
I would be happy for any tip.
INFO:
We are using the latest C# language version
The solution should support IntelliSense (e.g., Name and Info should be proposed to the user)
Edit:
I can't use a constructor because I only have an interface. No class/implementation. The framework behind creates the object to the given interface T bo = NewRow<T>();. Which actual object gets created is decided by the framework
Also initializers like { Name: myOtherVariable.FirstName } should be possible
an Action could be everything, not just a simple assignment. So if a client chosed to make a function-call instead, there literally is nothing to shortcut here. See this for example:
uow.NewRow<IWhatever>(() => Console.WriteLine("tataaaa"););
So no, what you want isn't possible.
However you could create some kind of EventsArgs that hold your names and use those within your NewRow-method. There's no need for an action if all those callbacks should actually be just assignement-calls alltogether.
uow.NewRow<ICustomer>(new MyArgs {
Name = "Zzz",
Info = "Abc"
});
And within NewRow:
public T NewRow<T>(MyArgs args) where T : IBo
{
customer.Name = args.Name;
customer.Info = args.Info;
}

Reading properties from TwinCAT function blocks using c#

We are using a C# application to read variables from a Beckhoff PLC through TwinCAT ADS v.3. If we attempt to utilize the same code to read properties the code fails with an exception.
FUNCTION_BLOCK FB_Sample
VAR
SomeVariable : INT;
END_VAR
PROPERTY SomeProp : INT // declared in a separate file
// Code used to read variable (symbol)
var handle = client.CreateVariableHandle("sampleProgram.Source.SomeVariable");
var result = client.ReadAny(handle, typeof(int));
client.DeleteVariableHandle(handle);
// Adapted code used to read property (not a symbol)
var handle = client.CreateVariableHandle("sampleProgram.Source.SomeProp"); // This fails
var result = client.ReadAny(handle, typeof(int));
client.DeleteVariableHandle(handle);
When trying to create a variable handle using the above code, we receive TwinCAT.Ads.AdsErrorException: 'Ads-Error 0x710 : Symbol could not be found.'.
Since we knew that METHOD must be marked with {attribute 'TcRpcEnable'} so it can be called with this code:
client.InvokeRpcMethod("{symbolPath}", "{methodName}", {parameters} });
We attempted to use that attribute {attribute 'TcRpcEnable'} on the property as well. Using TcAdsClient.CreateSymbolLoader and looping over all available symbols we discovered that the getter/setter of the property were then marked as rpc-methods.
Console.WriteLine($"Name: {rpcMethod.Name}; Parameters.Count: {rpcMethod.Parameters.Count}; ReturnType: {rpcMethod.ReturnType};");
RpcMethods: 2
Name: __setSomeProp; Parameters.Count: 1; ReturnType: ;
Name: __getSomeProp; Parameters.Count: 0; ReturnType: INT;
But try as we might, we cannot invoke the rpc method:
var propertyResult = client.InvokeRpcMethod("sampleProgram.Source", "__getSomeProp", Array.Empty<object>());
// Throws: TwinCAT.Ads.AdsErrorException: 'Ads-Error 0x710 : Symbol could not be found.'
var propertyResult = client.InvokeRpcMethod("sampleProgram.Source", "__get{SomeProp}", Array.Empty<object>());
// Throws: TwinCAT.Ads.RpcMethodNotSupportedException: 'The RPC method '__get{SomeProp}' is not supported on symbol 'sampleProgram.Source!'
var propertyResult = client.InvokeRpcMethod("sampleProgram.Source", "get{SomeProp}", Array.Empty<object>());
// Throws: TwinCAT.Ads.RpcMethodNotSupportedException: 'The RPC method 'get{SomeProp}' is not supported on symbol 'sampleProgram.Source!'
var propertyResult = client.InvokeRpcMethod("sampleProgram.Source.SomeProp", "get", Array.Empty<object>());
// Throws: System.ArgumentNullException: 'Value cannot be null.
// Parameter name: symbol'
Any suggestion on how we can read/write variables defined as properties on function blocks?
When you define a new property you automatically create a get and a set for that property.
You normally use properties to read or write variables that are in the VAR section of the function block.
All variables that are in the VAR section are private thus the need of properties to access those VARs from outside the Function Block.
Properties in theory should not do any complex calculations or run any logic unlike Methods.
The point I want to make is that you do not need and should not call properties via ADS.
You have access to all private VARs via ADS anyway so there is no need to call properties through ADS in the first place.
#Edit
I'm still of the opinion that properties should not contain any logic and therefor there is no need to call them via ADS.
Nevertheless there are always exceptions.
Be aware that according to the Beckhoff documentation, only simple data types and pointers will work, not structures.
Moreover "Function monitoring is not possible in the compact runtime system".
Here my working example after experimenting with the {attribute 'monitoring' := 'call'} attribute
In Twincat:
{attribute 'monitoring' := 'call'}
PROPERTY RemoteCall : INT
GET:
RemoteCall := buffer;
SET:
buffer := buffer + RemoteCall;
In C#
class Program
{
static TcAdsClient tcClient;
static void Main(string[] args)
{
tcClient = new TcAdsClient();
tcClient.Connect(851);
AdsStream dataStream = new AdsStream(2);
int iHandle = tcClient.CreateVariableHandle("MAIN.fbTest.RemoteCall");
tcClient.Read(iHandle, dataStream);
Console.WriteLine("Remote Var before property call: " + BitConverter.ToInt16(dataStream.ToArray(), 0));
tcClient.WriteAny(iHandle,Convert.ToInt16(2));
tcClient.Read(iHandle, dataStream);
Console.WriteLine("Remote Var after property call: " + BitConverter.ToInt16(dataStream.ToArray(), 0));
Console.WriteLine();
Console.ReadLine();
}
}
According to Stefan Hennecken on his Blog the property has to be decorated with a pragma to enable this:
{attribute ‘monitoring’ := ‘call’}
PROPERTY PUBLIC nProp : BYTE
Then it can be read/written with this code sample:
using (AdsClient client = new AdsClient())
{
byte valuePlc;
client.Connect(AmsNetId.Local, 851);
valuePlc = (byte)client.ReadValue(“MAIN.fbFoo.nProp”, typeof(byte));
client.WriteValue(“MAIN.fbFoo.nProp”, ++valuePlc);
}

Use Action instead of Func

This is from the factory pattern, where a property is used to get an instance via Create:
public class Dialer
{
public static Func<Dialer> Create;
public bool MakeCall(string number) ...
public Dialer(IDialer impl) { ... }
}
Then a lambda expression is assigned to the property delegate in the platform-specific project with
Dialer.Create = () => new Dialer(new PhoneDialeriOS());
and to get an instance in the platform-independent project I use
this.dialer = Dialer.Create();
Now I'm looking to use
public static Action<Dialer> Create;
If I get this right, the assignment now is
Dialer.Create = (d) => new Dialer(new PhoneDialeriOS());
but how do I get an instance?
this.dialer = // ?
By using this.dialer = Dialer.Create(); I get
Error CS7036 There is no argument given that corresponds to the required formal parameter 'obj' of 'Action'
But it doesn't make sense to pass an instance of PhoneDialeriOS here, because there is no access to it in the platform-independent code. I think the example I'm regarding to is misleading or I'm missing something.
Action<Dialer> is a a delegate that receives a Dialer instance, and returns void. It's an Action, after all. If you want it to return a value (and get an argument), you need to use Func<Dialer, Dialer> instead.
The following could be possible usages
var specific_dialer = new Dialer(new PhoneDialeriOS());
var defualt_dialer = Dialer.Create();
Edit
Of course you can do something like
Dialer.Create = () => new Dialer(new PhoneDialerAndroid());
without the (likely a wrong copy/paste) line with the Action

Is it possible to change cast of an object dynamically?

I want have a "pointer" to an object but the object can be one of two classes.
QuickFix.Message newOrderSingle;
if (ecn.versionFIX.Equals(VersionFIX.FSS_FIX44))
{
newOrderSingle = new QuickFix.FIX44.NewOrderSingle(
new ClOrdID(masterForm.OrderBook.GetNewClOrdIDBroker(ecn.brokerCode)),
new Symbol(symbol),
new Side(side),
new TransactTime(DateTime.Now),
ordType = new OrdType(OrdType.LIMIT));
}
else
{
newOrderSingle = new QuickFix.FIX42.NewOrderSingle(
new ClOrdID(masterForm.OrderBook.GetNewClOrdIDBroker(ecn.brokerCode)),
new HandlInst('1'),
new Symbol(symbol),
new Side(side),
new TransactTime(DateTime.Now),
ordType = new OrdType(OrdType.LIMIT));
}
Then later I want to do this, where "set" is a method of QuickFix.FIX44.NewOrderSingle:
newOrderSingle.Set(new Price(limitPrice));
Instead I have to do:
((QuickFix.FIX44.NewOrderSingle) newOrderSingle).Set(new Price(limitPrice));
Which is hard to read.
Can I change "cast" of NewOrderSingle dynamically in some way?
You have some options:
dynamic
You can use dynamic keyword to make "duck typing":
dynamic order= newOrderSingle;
order.Set(new Price(limitPrice));
Unfortunately, you loose intellisense and will get RuntimeBinderException when order has not such a method (is of type FIX42 f.e.).
GenericInvoker
You can use my library:
newOrderSingle.DetermineType()
.When((QuickFix.FIX42 msg) => msg.Set(/* ... */))
.Resolve();
Unfortunately, you need to hardcode type.
To sum up
If you need to use such approach, your classes are badly designed. Consider creating base / abstract class or some inteface:
interface IMessageSetable
{
Set(Price p);
}
public class FIX44 : IMessageSetable
{
// impl
}
then:
if (newOrderSingle is IMessageSetable)
((IMessageSetable)newOrderSingle).Set(price);
if you have access to the source code of QuickFix.Message, you can add it. perhaps you can add the set function to a common interface.
the really dirty way is using reflection. the code would look like this:
newOrderSingle.GetType().GetMethod("Set").Invoke(newOrderSingle, new Price(limitPrice)));
(I guess it will not compile directly, the function parameters needs to be adjusted)
you could also try to use the dynamic datatype
As long as you use the common base class QuickFix.Message you cannot use specific members without casting.
If you have a piece of code where you work with a specific subclass you can do:
if(newOrderSingle is QuickFix.FIX44.NewOrderSingle)
{
QuickFix.FIX44.NewOrderSingle ord44 = (QuickFix.FIX44.NewOrderSingle)newOrderSingle;
// from here on you can work with ord44:
ord44.Set(new Price(limitPrice));
// more code which uses ord44
}

The following throws 'is a Method but treated like a type'

The most confusing error I have ever seen in ASP. I have done method calls like this before, and have no issue in other spots of my code.
First of all the class:
namespace LocApp.Helpers.Classes.LocationHelper
{
public class QueryHelper
{
private LocAppContext db = new LocAppContext();
public static IEnumerable<Service> getAllService()
{
using (var db = new LocAppContext())
{
var service = db.Locations.Include(s => s.LocationAssignment);
var serv = (from s in db.Services
where s.active == true
select s).ToList();
return serv;
}
}
}
}
Pretty easy to understand whats going on. So lets call the method:
IEnumerable<LocApp.Models.Service> Service = new LocApp.Helpers.Classes.LocationHelper.QueryHelper.getAllService(Model.id);
getAllServices(Model.id) is throwing the error "is a method but treated like a type" , um no its not be treated like a type....
whats going on?
Well it's exactly as the error message says. getAllService() is a method:
public static IEnumerable<Service> getAllService()
But you're trying to use it as if it were a type with a constructor:
Service = new LocApp.Helpers.Classes.LocationHelper.QueryHelper.getAllService(...)
The new part is the mistake here. You don't want to call a constructor, you just want to call a method. It's a static method, so you don't need an instance - you can just use:
Service = LocApp.Helpers.Classes.LocationHelper.QueryHelper.getAllService(...)
Note that if you have appropriate using directives, follow .NET naming conventions and take care about singular/plural names, your code will be easier to follow:
var services = QueryHelper.GetAllServices(...);
Do you not simply mean:
IEnumerable<LocApp.Models.Service> Service = LocApp.Helpers.Classes.LocationHelper.QueryHelper.getAllService();
Get rid of the new bit, essentially, and that method doesn't take any parameters either - I'd assume you'd run into that problem after you removed the new bit.
Your getAllService method doesn't take any arguments, so you should call it without. Also it is a static method so don't use the new keyword:
IEnumerable<LocApp.Models.Service> Service = LocApp.Helpers.Classes.LocationHelper.QueryHelper.getAllService();

Categories