Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: C# - Runtime parsing of interpolation-tokenized string #2347

Closed
ghost opened this issue Mar 15, 2019 · 28 comments
Closed

Feature Request: C# - Runtime parsing of interpolation-tokenized string #2347

ghost opened this issue Mar 15, 2019 · 28 comments

Comments

@ghost
Copy link

ghost commented Mar 15, 2019

C# introduced interpolated strings (i.e. $"{varname}") back in 6.0. This is a feature request to extend it in a future iteration of the language/compiler. This could possibly be done using some manner of a compiler trick as well ?

Consider this snippet of code of the as-is scenario:

Snippet 1:

const string url = "https://login.microsoftonline.com/{tenantId}";
//…


string tenantId = "contoso.onmicrosoft.com";
//…
Uri u = new Uri( url.Replace("{tenantId}, tenantId) );

Now, wouldn't it be more useful (reducing lines of code and errors to do with string replacements if this could simply be something like:

Snippet 2:

const string url = "https://login.microsoftonline.com/{tenantId}";
//…


string tenantId = "contoso.onmicrosoft.com";
//…
Uri u = new Uri( $url );

Note that in the 2nd snippet, I have prefixed the url variable in the Uri constructor with a $ indicating that I want the compiler to perform the intended magic.

Can this be?

This item was moved from dotnet/roslyn#34145

@HaloFour
Copy link
Contributor

How does that work? Is it expected that the variable or expression embedded within the interpolating string constant will be valid at the point where you interpolate? Where does templateId come from? What if it is a more complicated expression, or a method call? How would that work in a separate class or across assemblies?

@ghost
Copy link
Author

ghost commented Mar 15, 2019

It would work at that place, in that context. In any other place where the interpolated expression is not a valid in-scope resultant, it would throw an error exactly like it does today.

string foo = $"{gah}";

If gah is valid, interpolate and use. Otherwise you get errors.

Only change per my idea is to allow use of a predeclared string variable in the place of a string literal.

@HaloFour
Copy link
Contributor

Only change per my idea is to allow use of a predeclared string variable in the place of a string literal.

It gets a lot more complicated than that because string interpolation allows you to put quite literally any expression in the string, not just variable names. That's problematic because the compiler can't do anything to ensure that whatever expression is in that string is valid because it has no idea what those identifiers are, and it opens avenues where a string constant could embed malicious or deleterious code into the consuming program. Not to mention, no other language could consume those constants.

@ghost
Copy link
Author

ghost commented Mar 15, 2019

I agree with you. But I still don't understand how that's different from writing the string "there" (versus using a string in a variable). Could you explain that?

@HaloFour
Copy link
Contributor

But I still don't understand how that's different from writing the string "there" (versus using a string in a variable). Could you explain that?

Sure, when you write an interpolated string now the expression within the string has to be validated by the compiler. You can't reference variables that don't exist or embed invalid code.

If that code was instead embedded in a const string the compiler can't validate it at that point because those variables don't exist. This would be asking for the compiler to just embed that code as code within the constant and then reinterpret it if you happen to use the constant in an interpolated string. The code could mean (and do) wildly different things at each point you used it. It could even contain malicious code. And if you accidentally forget to interpolate you could expose the code directly.

@ghost
Copy link
Author

ghost commented Mar 15, 2019

Okay. Got it.

But the language has so many strange things in it today that would've sounded absurd a couple of years ago.

Am happy to wait for this 2-3 releases from now when we might have more going on that pushes this into the realm ofpossible.

Put it on some backlog :-)

@svick
Copy link
Contributor

svick commented Mar 15, 2019

@sujayvsarma I believe the issues mentioned by @HaloFour don't make this feature impossible, they make it undesirable.

Look at it this way: what you're asking for is effectively to have a "function", but one whose arguments are not specified explicitly, but implicitly based on their names. Such tight coupling between a "function" and the code that calls it is not a good idea.

It also means that this feature would not be friendly to IntelliSense: when you're writing the const string, IntelliSense could not help you at all.

@ghost
Copy link
Author

ghost commented Mar 15, 2019

We have too many of those in C# already @svick.

@scalablecory
Copy link

I don't really understand the key benefit of a late-bound interpolation like this. It sounds far more error-prone -- we should endeavor to catch as many errors at compile time as possible.

Also, no other expression in C# allows you to use identifiers that don't exist yet, so this isn't exactly idiomatic.

@ghost
Copy link
Author

ghost commented Mar 16, 2019

I'd like someone from the C# team to weigh in on this.

@vbcodec
Copy link

vbcodec commented Mar 16, 2019

this is usefull feature, but any unique string (template) will require dynamic compilation, and performance may be terrible, at lest for first run

@vladd
Copy link

vladd commented Mar 16, 2019

@sujayvsarma Actually you are asking for eval, right?

@scalablecory
Copy link

scalablecory commented Mar 16, 2019

If you're looking to provide some sort of templating support, consider using Roslyn's C# Scripting.

@ghost
Copy link
Author

ghost commented Mar 17, 2019

@sujayvsarma Actually you are asking for eval, right?

Looks similar yes, but that's NOT what am asking for. My intention is an extension of where interpolated strings are at today. It is a logical next step.

@HaloFour
Copy link
Contributor

It is a logical next step.

I don't see how it is. There's no where else in C# where you can define a completely unverifiable expression to have it interpreted at the call-site. That would behave painfully similar to C macros and would probably be quite difficult to troubleshoot when some part of that expression fails to parse at compile-time. It's not as nefarious as, say, an "eval" statement, but it makes it impossible to provide a good tooling or design time support and enables some serious "spooky action at a distance".

@gafter
Copy link
Member

gafter commented Mar 17, 2019

There's no where else in C# where you can define a completely unverifiable expression to have it interpreted at the call-site.

Yes there is. dynamic.

@HaloFour
Copy link
Contributor

@gafter

Yes there is. dynamic.

Touché, although I don't think that's quite analogous given that dynamic defers evaluation until runtime and does not involve parsing expressions out of strings potentially defined in other assemblies silently enclosing over identifiers that happen to be in scope. I should probably append "by the compiler" to my statement.

@theunrepentantgeek
Copy link

theunrepentantgeek commented Mar 17, 2019

@HaloFour, I don't think your statement needs modification.

Broadly speaking, the behaviour of dynamic is to defer member selection to runtime. The syntax of the expression is still verified by the compiler and you will get an error if it doesn't comply with C# expectations. #iknowyouknowthis

@dsaf
Copy link

dsaf commented Mar 18, 2019

The language already supports this:

string url(string tenantId) => $"https://login.microsoftonline.com/{tenantId}";
//…

string tenId = "contoso.onmicrosoft.com";
//…

Uri u = new Uri(url(tenId));

@ghost
Copy link
Author

ghost commented Mar 18, 2019

The language already supports this:
string url(string tenantId) => $"https://login.microsoftonline.com/{tenantId}";
//…

string tenId = "contoso.onmicrosoft.com";
//…

Uri u = new Uri(url(tenId));

THAT is not the same thing. All you are doing is using a shortcut to define a function and use that. Also, the resulting behaviour is not what I was asking for.

@yaakov-h
Copy link
Member

You could use current language features to rebuilt your string, like so:

var tenantId = "placeholder";

FormattableString f = $"https://login.microsoftonline.com/{tenantId}";
var newUri = string.Format(f.Format, "myNewTenantId");

Uri u = new Uri(newUri);

But as per your proposal, having the language be able to pull variables that have not been defined yet seems like a confusing recipe for subtle bugs.

@Thaina
Copy link

Thaina commented Mar 19, 2019

I would really appreciate this feature

However, it is a runtime feature. So in anyway the place that this feature would be implemented will be corefx. Not the language feature itself

@juliusfriedman
Copy link

juliusfriedman commented Mar 31, 2019

Can't you just use a conditional operator in the interpolated string?

https://stackoverflow.com/questions/33473752/c-sharp-interpolated-string-with-conditional-operator

iMHO the string interpolation shouldn't get access to anything which is not defined in it's template but should be able to support more or less template arguments than given especially with the correct logic in the formatting.

You could give your interpolated string a special variable you can control and format as you desire...

Perhaps what I would like to see with interpolated string is at runtime a way to enumerate their symbols and expressions but that's not in line with this proposal as it stands...

@giuliov
Copy link

giuliov commented May 21, 2020

I found this very good solution in Stack Overflow. Copying here for easier reading

You can use reflection coupled with an anonymous type to do this:

public string StringFormat(string input, object parameters)
{
    var properties = parameters.GetType().GetProperties();
    var result = input;

    foreach (var property in properties)
    {
        result = result.Replace(
            $"{{{{{property.Name}}}}}", //This is assuming your param names are in format "{{abc}}"
            property.GetValue(parameters).ToString());
    }

    return result;
}

And call it like this: var result = StringFormat(retrievedString, new { parameter1, parameter2 });

@Thaina
Copy link

Thaina commented May 21, 2020

This is what I have did though

It use regex to search for {key} or {key:format} then you get key to lookup by provide Func callback

	static readonly Regex FormatterPattern = new Regex(@"\{([^\{\}]+?)(?:\:([^\{\}]*))?\}",RegexOptions.Multiline);
	public static string RegexFormat(this string input,Func<string,object> selector)
	{
		return FormatterPattern.Replace(input,(match) => {
			var capture = match?.Groups?.OfType<Group>().Skip(1).Select((group) => group.Value).ToArray();
			if(!(capture?.FirstOrDefault() is string key && selector(key) is var value && value != null))
				return match.Value;

			return capture.Length > 1 && value is IFormattable formattable ? formattable.ToString(capture[1],null) : value?.ToString();
		});
	}
string text;
int number;
text.RegexFormat("Value : {number:0.0####}",(key) => key == "number" ? 10 : "novalue");

So we could use class or complex data of json or anything

Maybe I should also guard that if there is {{key}} then it will just return {key}

@IanKemp
Copy link

IanKemp commented Sep 15, 2020

This would be useful for resource strings. IMO, what's missing is the ability to convert an interpolated format string like "https://login.microsoftonline.com/{tenantId}" to a standard "dumb" format string like "https://login.microsoftonline.com/{0}".

@jnm2
Copy link
Contributor

jnm2 commented Sep 15, 2020

Seems like a utility that belongs in a library or the BCL.

@YairHalberstadt
Copy link
Contributor

Closing as due to a github bug I can't move this to a discussion, and this issue doesn't have a huge amount of support. Feel free to open a new discussion and reference this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests